diff options
author | jamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-25 00:09:14 +0000 |
---|---|---|
committer | jamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-25 00:09:14 +0000 |
commit | 94f206c1c75eb8cc4df2225a1c5c9c7b6fc96679 (patch) | |
tree | 530f51d5c75459999e4adf2a6895884ce1c15ce0 | |
parent | 56235947f2b023fc63cfad692c56df4e92199848 (diff) | |
download | chromium_src-94f206c1c75eb8cc4df2225a1c5c9c7b6fc96679.zip chromium_src-94f206c1c75eb8cc4df2225a1c5c9c7b6fc96679.tar.gz chromium_src-94f206c1c75eb8cc4df2225a1c5c9c7b6fc96679.tar.bz2 |
Here are gyp targets and stubs for compiling libcc and the webkit_compositor bindings in chromium. Everything is guarded behind the off-by-default use_libcc_for_compositor gyp variable. I haven't included the actual code here, but there are scripts to sync. I plan to land + manually sync the code into place until we're ready to flip the gyp switch.
Snapshot from r126652
BUG=
Review URL: https://chromiumcodereview.appspot.com/10828381
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@153354 0039d316-1c4b-4281-b951-d872f2087c98
302 files changed, 55916 insertions, 5 deletions
diff --git a/PRESUBMIT.py b/PRESUBMIT.py index 0ae66e7..e99161c 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py @@ -22,6 +22,7 @@ _EXCLUDED_PATHS = ( r"^v8[\\\/].*", r".*MakeFile$", r".+_autogen\.h$", + r"^cc[\\\/].*", ) diff --git a/build/all.gyp b/build/all.gyp index 947b43d..2db7070 100644 --- a/build/all.gyp +++ b/build/all.gyp @@ -31,6 +31,7 @@ # as gyp files come online. ['OS!="ios"', { 'dependencies': [ + '../cc/cc_tests.gyp:*', '../chrome/chrome.gyp:*', '../content/content.gyp:*', '../gpu/gpu.gyp:*', diff --git a/cc/BitmapCanvasLayerTextureUpdater.cpp b/cc/BitmapCanvasLayerTextureUpdater.cpp new file mode 100644 index 0000000..240543f --- /dev/null +++ b/cc/BitmapCanvasLayerTextureUpdater.cpp @@ -0,0 +1,89 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "BitmapCanvasLayerTextureUpdater.h" + +#include "LayerPainterChromium.h" +#include "PlatformColor.h" +#include "skia/ext/platform_canvas.h" + +namespace WebCore { + +BitmapCanvasLayerTextureUpdater::Texture::Texture(BitmapCanvasLayerTextureUpdater* textureUpdater, PassOwnPtr<CCPrioritizedTexture> texture) + : LayerTextureUpdater::Texture(texture) + , m_textureUpdater(textureUpdater) +{ +} + +BitmapCanvasLayerTextureUpdater::Texture::~Texture() +{ +} + +void BitmapCanvasLayerTextureUpdater::Texture::updateRect(CCResourceProvider* resourceProvider, const IntRect& sourceRect, const IntSize& destOffset) +{ + textureUpdater()->updateTextureRect(resourceProvider, texture(), sourceRect, destOffset); +} + +PassRefPtr<BitmapCanvasLayerTextureUpdater> BitmapCanvasLayerTextureUpdater::create(PassOwnPtr<LayerPainterChromium> painter) +{ + return adoptRef(new BitmapCanvasLayerTextureUpdater(painter)); +} + +BitmapCanvasLayerTextureUpdater::BitmapCanvasLayerTextureUpdater(PassOwnPtr<LayerPainterChromium> painter) + : CanvasLayerTextureUpdater(painter) + , m_opaque(false) +{ +} + +BitmapCanvasLayerTextureUpdater::~BitmapCanvasLayerTextureUpdater() +{ +} + +PassOwnPtr<LayerTextureUpdater::Texture> BitmapCanvasLayerTextureUpdater::createTexture(CCPrioritizedTextureManager* manager) +{ + return adoptPtr(new Texture(this, CCPrioritizedTexture::create(manager))); +} + +LayerTextureUpdater::SampledTexelFormat BitmapCanvasLayerTextureUpdater::sampledTexelFormat(GC3Denum textureFormat) +{ + // The component order may be bgra if we uploaded bgra pixels to rgba textures. + return PlatformColor::sameComponentOrder(textureFormat) ? + LayerTextureUpdater::SampledTexelFormatRGBA : LayerTextureUpdater::SampledTexelFormatBGRA; +} + +void BitmapCanvasLayerTextureUpdater::prepareToUpdate(const IntRect& contentRect, const IntSize& tileSize, float contentsWidthScale, float contentsHeightScale, IntRect& resultingOpaqueRect, CCRenderingStats& stats) +{ + if (m_canvasSize != contentRect.size()) { + m_canvasSize = contentRect.size(); + m_canvas = adoptPtr(skia::CreateBitmapCanvas(m_canvasSize.width(), m_canvasSize.height(), m_opaque)); + } + + paintContents(m_canvas.get(), contentRect, contentsWidthScale, contentsHeightScale, resultingOpaqueRect, stats); +} + +void BitmapCanvasLayerTextureUpdater::updateTextureRect(CCResourceProvider* resourceProvider, CCPrioritizedTexture* texture, const IntRect& sourceRect, const IntSize& destOffset) +{ + const SkBitmap& bitmap = m_canvas->getDevice()->accessBitmap(false); + bitmap.lockPixels(); + + texture->upload(resourceProvider, static_cast<const uint8_t*>(bitmap.getPixels()), contentRect(), sourceRect, destOffset); + bitmap.unlockPixels(); +} + +void BitmapCanvasLayerTextureUpdater::setOpaque(bool opaque) +{ + if (opaque != m_opaque) { + m_canvas.clear(); + m_canvasSize = IntSize(); + } + m_opaque = opaque; +} + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/BitmapCanvasLayerTextureUpdater.h b/cc/BitmapCanvasLayerTextureUpdater.h new file mode 100644 index 0000000..233c83b --- /dev/null +++ b/cc/BitmapCanvasLayerTextureUpdater.h @@ -0,0 +1,57 @@ +// 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. + + +#ifndef BitmapCanvasLayerTextureUpdater_h +#define BitmapCanvasLayerTextureUpdater_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "CanvasLayerTextureUpdater.h" + +class SkCanvas; + +namespace WebCore { + +class LayerPainterChromium; + +// This class rasterizes the contentRect into a skia bitmap canvas. It then updates +// textures by copying from the canvas into the texture, using MapSubImage if +// possible. +class BitmapCanvasLayerTextureUpdater : public CanvasLayerTextureUpdater { +public: + class Texture : public LayerTextureUpdater::Texture { + public: + Texture(BitmapCanvasLayerTextureUpdater*, PassOwnPtr<CCPrioritizedTexture>); + virtual ~Texture(); + + virtual void updateRect(CCResourceProvider*, const IntRect& sourceRect, const IntSize& destOffset) OVERRIDE; + + private: + BitmapCanvasLayerTextureUpdater* textureUpdater() { return m_textureUpdater; } + + BitmapCanvasLayerTextureUpdater* m_textureUpdater; + }; + + static PassRefPtr<BitmapCanvasLayerTextureUpdater> create(PassOwnPtr<LayerPainterChromium>); + virtual ~BitmapCanvasLayerTextureUpdater(); + + virtual PassOwnPtr<LayerTextureUpdater::Texture> createTexture(CCPrioritizedTextureManager*) OVERRIDE; + virtual SampledTexelFormat sampledTexelFormat(GC3Denum textureFormat) OVERRIDE; + virtual void prepareToUpdate(const IntRect& contentRect, const IntSize& tileSize, float contentsWidthScale, float contentsHeightScale, IntRect& resultingOpaqueRect, CCRenderingStats&) OVERRIDE; + void updateTextureRect(CCResourceProvider*, CCPrioritizedTexture*, const IntRect& sourceRect, const IntSize& destOffset); + + virtual void setOpaque(bool) OVERRIDE; + +private: + explicit BitmapCanvasLayerTextureUpdater(PassOwnPtr<LayerPainterChromium>); + + OwnPtr<SkCanvas> m_canvas; + IntSize m_canvasSize; + bool m_opaque; +}; + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) +#endif // BitmapCanvasLayerTextureUpdater_h diff --git a/cc/BitmapSkPictureCanvasLayerTextureUpdater.cpp b/cc/BitmapSkPictureCanvasLayerTextureUpdater.cpp new file mode 100644 index 0000000..1d0c913 --- /dev/null +++ b/cc/BitmapSkPictureCanvasLayerTextureUpdater.cpp @@ -0,0 +1,84 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "BitmapSkPictureCanvasLayerTextureUpdater.h" + +#include "CCRenderingStats.h" +#include "LayerPainterChromium.h" +#include "PlatformColor.h" +#include "SkCanvas.h" +#include "SkDevice.h" +#include <wtf/CurrentTime.h> + +namespace WebCore { + +BitmapSkPictureCanvasLayerTextureUpdater::Texture::Texture(BitmapSkPictureCanvasLayerTextureUpdater* textureUpdater, PassOwnPtr<CCPrioritizedTexture> texture) + : CanvasLayerTextureUpdater::Texture(texture) + , m_textureUpdater(textureUpdater) +{ +} + +void BitmapSkPictureCanvasLayerTextureUpdater::Texture::prepareRect(const IntRect& sourceRect, CCRenderingStats& stats) +{ + m_bitmap.setConfig(SkBitmap::kARGB_8888_Config, sourceRect.width(), sourceRect.height()); + m_bitmap.allocPixels(); + m_bitmap.setIsOpaque(m_textureUpdater->layerIsOpaque()); + SkDevice device(m_bitmap); + SkCanvas canvas(&device); + double paintBeginTime = monotonicallyIncreasingTime(); + textureUpdater()->paintContentsRect(&canvas, sourceRect, stats); + stats.totalPaintTimeInSeconds += monotonicallyIncreasingTime() - paintBeginTime; +} + +void BitmapSkPictureCanvasLayerTextureUpdater::Texture::updateRect(CCResourceProvider* resourceProvider, const IntRect& sourceRect, const IntSize& destOffset) +{ + m_bitmap.lockPixels(); + texture()->upload(resourceProvider, static_cast<uint8_t*>(m_bitmap.getPixels()), sourceRect, sourceRect, destOffset); + m_bitmap.unlockPixels(); + m_bitmap.reset(); +} + +PassRefPtr<BitmapSkPictureCanvasLayerTextureUpdater> BitmapSkPictureCanvasLayerTextureUpdater::create(PassOwnPtr<LayerPainterChromium> painter) +{ + return adoptRef(new BitmapSkPictureCanvasLayerTextureUpdater(painter)); +} + +BitmapSkPictureCanvasLayerTextureUpdater::BitmapSkPictureCanvasLayerTextureUpdater(PassOwnPtr<LayerPainterChromium> painter) + : SkPictureCanvasLayerTextureUpdater(painter) +{ +} + +BitmapSkPictureCanvasLayerTextureUpdater::~BitmapSkPictureCanvasLayerTextureUpdater() +{ +} + +PassOwnPtr<LayerTextureUpdater::Texture> BitmapSkPictureCanvasLayerTextureUpdater::createTexture(CCPrioritizedTextureManager* manager) +{ + return adoptPtr(new Texture(this, CCPrioritizedTexture::create(manager))); +} + +LayerTextureUpdater::SampledTexelFormat BitmapSkPictureCanvasLayerTextureUpdater::sampledTexelFormat(GC3Denum textureFormat) +{ + // The component order may be bgra if we uploaded bgra pixels to rgba textures. + return PlatformColor::sameComponentOrder(textureFormat) ? + LayerTextureUpdater::SampledTexelFormatRGBA : LayerTextureUpdater::SampledTexelFormatBGRA; +} + +void BitmapSkPictureCanvasLayerTextureUpdater::paintContentsRect(SkCanvas* canvas, const IntRect& sourceRect, CCRenderingStats& stats) +{ + // Translate the origin of contentRect to that of sourceRect. + canvas->translate(contentRect().x() - sourceRect.x(), + contentRect().y() - sourceRect.y()); + double rasterizeBeginTime = monotonicallyIncreasingTime(); + drawPicture(canvas); + stats.totalRasterizeTimeInSeconds += monotonicallyIncreasingTime() - rasterizeBeginTime; +} + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/BitmapSkPictureCanvasLayerTextureUpdater.h b/cc/BitmapSkPictureCanvasLayerTextureUpdater.h new file mode 100644 index 0000000..4718d85 --- /dev/null +++ b/cc/BitmapSkPictureCanvasLayerTextureUpdater.h @@ -0,0 +1,45 @@ +// 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. + + +#ifndef BitmapSkPictureCanvasLayerTextureUpdater_h +#define BitmapSkPictureCanvasLayerTextureUpdater_h + +#if USE(ACCELERATED_COMPOSITING) +#include "SkBitmap.h" +#include "SkPictureCanvasLayerTextureUpdater.h" + +namespace WebCore { + +// This class records the contentRect into an SkPicture, then software rasterizes +// the SkPicture into bitmaps for each tile. This implements CCSettings::perTilePainting. +class BitmapSkPictureCanvasLayerTextureUpdater : public SkPictureCanvasLayerTextureUpdater { +public: + class Texture : public CanvasLayerTextureUpdater::Texture { + public: + Texture(BitmapSkPictureCanvasLayerTextureUpdater*, PassOwnPtr<CCPrioritizedTexture>); + + virtual void prepareRect(const IntRect& sourceRect, CCRenderingStats&) OVERRIDE; + virtual void updateRect(CCResourceProvider*, const IntRect& sourceRect, const IntSize& destOffset) OVERRIDE; + + private: + BitmapSkPictureCanvasLayerTextureUpdater* textureUpdater() { return m_textureUpdater; } + + SkBitmap m_bitmap; + BitmapSkPictureCanvasLayerTextureUpdater* m_textureUpdater; + }; + + static PassRefPtr<BitmapSkPictureCanvasLayerTextureUpdater> create(PassOwnPtr<LayerPainterChromium>); + virtual ~BitmapSkPictureCanvasLayerTextureUpdater(); + + virtual PassOwnPtr<LayerTextureUpdater::Texture> createTexture(CCPrioritizedTextureManager*) OVERRIDE; + virtual SampledTexelFormat sampledTexelFormat(GC3Denum textureFormat) OVERRIDE; + void paintContentsRect(SkCanvas*, const IntRect& sourceRect, CCRenderingStats&); + +private: + explicit BitmapSkPictureCanvasLayerTextureUpdater(PassOwnPtr<LayerPainterChromium>); +}; +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) +#endif // BitmapSkPictureCanvasLayerTextureUpdater_h diff --git a/cc/CCActiveAnimation.cpp b/cc/CCActiveAnimation.cpp new file mode 100644 index 0000000..87086a3 --- /dev/null +++ b/cc/CCActiveAnimation.cpp @@ -0,0 +1,207 @@ +// Copyright 2012 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 "CCActiveAnimation.h" + +#include "CCAnimationCurve.h" +#include "TraceEvent.h" +#include <cmath> +#include <wtf/Assertions.h> +#include <wtf/StdLibExtras.h> +#include <wtf/StringExtras.h> + +namespace { + +// This should match the RunState enum. +static const char* const s_runStateNames[] = { + "WaitingForNextTick", + "WaitingForTargetAvailability", + "WaitingForStartTime", + "WaitingForDeletion", + "Running", + "Paused", + "Finished", + "Aborted" +}; + +COMPILE_ASSERT(static_cast<int>(WebCore::CCActiveAnimation::RunStateEnumSize) == WTF_ARRAY_LENGTH(s_runStateNames), RunState_names_match_enum); + +// This should match the TargetProperty enum. +static const char* const s_targetPropertyNames[] = { + "Transform", + "Opacity" +}; + +COMPILE_ASSERT(static_cast<int>(WebCore::CCActiveAnimation::TargetPropertyEnumSize) == WTF_ARRAY_LENGTH(s_targetPropertyNames), TargetProperty_names_match_enum); + +} // namespace + +namespace WebCore { + +PassOwnPtr<CCActiveAnimation> CCActiveAnimation::create(PassOwnPtr<CCAnimationCurve> curve, int animationId, int groupId, TargetProperty targetProperty) +{ + return adoptPtr(new CCActiveAnimation(curve, animationId, groupId, targetProperty)); +} + +CCActiveAnimation::CCActiveAnimation(PassOwnPtr<CCAnimationCurve> curve, int animationId, int groupId, TargetProperty targetProperty) + : m_curve(curve) + , m_id(animationId) + , m_group(groupId) + , m_targetProperty(targetProperty) + , m_runState(WaitingForTargetAvailability) + , m_iterations(1) + , m_startTime(0) + , m_alternatesDirection(false) + , m_timeOffset(0) + , m_needsSynchronizedStartTime(false) + , m_suspended(false) + , m_pauseTime(0) + , m_totalPausedTime(0) + , m_isControllingInstance(false) +{ +} + +CCActiveAnimation::~CCActiveAnimation() +{ + if (m_runState == Running || m_runState == Paused) + setRunState(Aborted, 0); +} + +void CCActiveAnimation::setRunState(RunState runState, double monotonicTime) +{ + if (m_suspended) + return; + + char nameBuffer[256]; + snprintf(nameBuffer, sizeof(nameBuffer), "%s-%d%s", s_targetPropertyNames[m_targetProperty], m_group, m_isControllingInstance ? "(impl)" : ""); + + bool isWaitingToStart = m_runState == WaitingForNextTick + || m_runState == WaitingForTargetAvailability + || m_runState == WaitingForStartTime; + + if (isWaitingToStart && runState == Running) + TRACE_EVENT_ASYNC_BEGIN1("cc", "CCActiveAnimation", this, "Name", TRACE_STR_COPY(nameBuffer)); + + bool wasFinished = isFinished(); + + const char* oldRunStateName = s_runStateNames[m_runState]; + + if (runState == Running && m_runState == Paused) + m_totalPausedTime += monotonicTime - m_pauseTime; + else if (runState == Paused) + m_pauseTime = monotonicTime; + m_runState = runState; + + const char* newRunStateName = s_runStateNames[runState]; + + if (!wasFinished && isFinished()) + TRACE_EVENT_ASYNC_END0("cc", "CCActiveAnimation", this); + + char stateBuffer[256]; + snprintf(stateBuffer, sizeof(stateBuffer), "%s->%s", oldRunStateName, newRunStateName); + + TRACE_EVENT_INSTANT2("cc", "CCLayerAnimationController::setRunState", "Name", TRACE_STR_COPY(nameBuffer), "State", TRACE_STR_COPY(stateBuffer)); +} + +void CCActiveAnimation::suspend(double monotonicTime) +{ + setRunState(Paused, monotonicTime); + m_suspended = true; +} + +void CCActiveAnimation::resume(double monotonicTime) +{ + m_suspended = false; + setRunState(Running, monotonicTime); +} + +bool CCActiveAnimation::isFinishedAt(double monotonicTime) const +{ + if (isFinished()) + return true; + + if (m_needsSynchronizedStartTime) + return false; + + return m_runState == Running + && m_iterations >= 0 + && m_iterations * m_curve->duration() <= monotonicTime - startTime() - m_totalPausedTime; +} + +double CCActiveAnimation::trimTimeToCurrentIteration(double monotonicTime) const +{ + double trimmed = monotonicTime + m_timeOffset; + + // If we're paused, time is 'stuck' at the pause time. + if (m_runState == Paused) + trimmed = m_pauseTime; + + // Returned time should always be relative to the start time and should subtract + // all time spent paused. + trimmed -= m_startTime + m_totalPausedTime; + + // Zero is always the start of the animation. + if (trimmed <= 0) + return 0; + + // Always return zero if we have no iterations. + if (!m_iterations) + return 0; + + // If less than an iteration duration, just return trimmed. + if (trimmed < m_curve->duration()) + return trimmed; + + // If greater than or equal to the total duration, return iteration duration. + if (m_iterations >= 0 && trimmed >= m_curve->duration() * m_iterations) { + if (m_alternatesDirection && !(m_iterations % 2)) + return 0; + return m_curve->duration(); + } + + // We need to know the current iteration if we're alternating. + int iteration = static_cast<int>(trimmed / m_curve->duration()); + + // Calculate x where trimmed = x + n * m_curve->duration() for some positive integer n. + trimmed = fmod(trimmed, m_curve->duration()); + + // If we're alternating and on an odd iteration, reverse the direction. + if (m_alternatesDirection && iteration % 2 == 1) + return m_curve->duration() - trimmed; + + return trimmed; +} + +PassOwnPtr<CCActiveAnimation> CCActiveAnimation::clone(InstanceType instanceType) const +{ + return cloneAndInitialize(instanceType, m_runState, m_startTime); +} + +PassOwnPtr<CCActiveAnimation> CCActiveAnimation::cloneAndInitialize(InstanceType instanceType, RunState initialRunState, double startTime) const +{ + OwnPtr<CCActiveAnimation> toReturn(adoptPtr(new CCActiveAnimation(m_curve->clone(), m_id, m_group, m_targetProperty))); + toReturn->m_runState = initialRunState; + toReturn->m_iterations = m_iterations; + toReturn->m_startTime = startTime; + toReturn->m_pauseTime = m_pauseTime; + toReturn->m_totalPausedTime = m_totalPausedTime; + toReturn->m_timeOffset = m_timeOffset; + toReturn->m_alternatesDirection = m_alternatesDirection; + toReturn->m_isControllingInstance = instanceType == ControllingInstance; + return toReturn.release(); +} + +void CCActiveAnimation::pushPropertiesTo(CCActiveAnimation* other) const +{ + // Currently, we only push changes due to pausing and resuming animations on the main thread. + if (m_runState == CCActiveAnimation::Paused || other->m_runState == CCActiveAnimation::Paused) { + other->m_runState = m_runState; + other->m_pauseTime = m_pauseTime; + other->m_totalPausedTime = m_totalPausedTime; + } +} + +} // namespace WebCore diff --git a/cc/CCActiveAnimation.h b/cc/CCActiveAnimation.h new file mode 100644 index 0000000..66c2d6d --- /dev/null +++ b/cc/CCActiveAnimation.h @@ -0,0 +1,161 @@ +// Copyright 2012 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. + +#ifndef CCActiveAnimation_h +#define CCActiveAnimation_h + +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCAnimationCurve; + +// A CCActiveAnimation, contains all the state required to play a CCAnimationCurve. +// Specifically, the affected property, the run state (paused, finished, etc.), +// loop count, last pause time, and the total time spent paused. +class CCActiveAnimation { + WTF_MAKE_NONCOPYABLE(CCActiveAnimation); +public: + // Animations begin in one of the 'waiting' states. Animations waiting for the next tick + // will start the next time the controller animates. Animations waiting for target + // availibility will run as soon as their target property is free (and all the animations + // animating with it are also able to run). Animations waiting for their start time to + // come have be scheduled to run at a particular point in time. When this time arrives, + // the controller will move the animations into the Running state. Running animations + // may toggle between Running and Paused, and may be stopped by moving into either the + // Aborted or Finished states. A Finished animation was allowed to run to completion, but + // an Aborted animation was not. + enum RunState { + WaitingForNextTick = 0, + WaitingForTargetAvailability, + WaitingForStartTime, + WaitingForDeletion, + Running, + Paused, + Finished, + Aborted, + // This sentinel must be last. + RunStateEnumSize + }; + + enum TargetProperty { + Transform = 0, + Opacity, + // This sentinel must be last. + TargetPropertyEnumSize + }; + + static PassOwnPtr<CCActiveAnimation> create(PassOwnPtr<CCAnimationCurve>, int animationId, int groupId, TargetProperty); + + virtual ~CCActiveAnimation(); + + int id() const { return m_id; } + int group() const { return m_group; } + TargetProperty targetProperty() const { return m_targetProperty; } + + RunState runState() const { return m_runState; } + void setRunState(RunState, double monotonicTime); + + // This is the number of times that the animation will play. If this + // value is zero the animation will not play. If it is negative, then + // the animation will loop indefinitely. + int iterations() const { return m_iterations; } + void setIterations(int n) { m_iterations = n; } + + double startTime() const { return m_startTime; } + void setStartTime(double monotonicTime) { m_startTime = monotonicTime; } + bool hasSetStartTime() const { return m_startTime; } + + double timeOffset() const { return m_timeOffset; } + void setTimeOffset(double monotonicTime) { m_timeOffset = monotonicTime; } + + void suspend(double monotonicTime); + void resume(double monotonicTime); + + // If alternatesDirection is true, on odd numbered iterations we reverse the curve. + bool alternatesDirection() const { return m_alternatesDirection; } + void setAlternatesDirection(bool alternates) { m_alternatesDirection = alternates; } + + bool isFinishedAt(double monotonicTime) const; + bool isFinished() const { return m_runState == Finished + || m_runState == Aborted + || m_runState == WaitingForDeletion; } + + CCAnimationCurve* curve() { return m_curve.get(); } + const CCAnimationCurve* curve() const { return m_curve.get(); } + + // If this is true, even if the animation is running, it will not be tickable until + // it is given a start time. This is true for animations running on the main thread. + bool needsSynchronizedStartTime() const { return m_needsSynchronizedStartTime; } + void setNeedsSynchronizedStartTime(bool needsSynchronizedStartTime) { m_needsSynchronizedStartTime = needsSynchronizedStartTime; } + + // Takes the given absolute time, and using the start time and the number + // of iterations, returns the relative time in the current iteration. + double trimTimeToCurrentIteration(double monotonicTime) const; + + enum InstanceType { + ControllingInstance = 0, + NonControllingInstance + }; + + PassOwnPtr<CCActiveAnimation> clone(InstanceType) const; + PassOwnPtr<CCActiveAnimation> cloneAndInitialize(InstanceType, RunState initialRunState, double startTime) const; + bool isControllingInstance() const { return m_isControllingInstance; } + + void pushPropertiesTo(CCActiveAnimation*) const; + +private: + CCActiveAnimation(PassOwnPtr<CCAnimationCurve>, int animationId, int groupId, TargetProperty); + + OwnPtr<CCAnimationCurve> m_curve; + + // IDs are not necessarily unique. + int m_id; + + // Animations that must be run together are called 'grouped' and have the same group id + // Grouped animations are guaranteed to start at the same time and no other animations + // may animate any of the group's target properties until all animations in the + // group have finished animating. Note: an active animation's group id and target + // property uniquely identify that animation. + int m_group; + + TargetProperty m_targetProperty; + RunState m_runState; + int m_iterations; + double m_startTime; + bool m_alternatesDirection; + + // The time offset effectively pushes the start of the animation back in time. This is + // used for resuming paused animations -- an animation is added with a non-zero time + // offset, causing the animation to skip ahead to the desired point in time. + double m_timeOffset; + + bool m_needsSynchronizedStartTime; + + // When an animation is suspended, it behaves as if it is paused and it also ignores + // all run state changes until it is resumed. This is used for testing purposes. + bool m_suspended; + + // These are used in trimTimeToCurrentIteration to account for time + // spent while paused. This is not included in AnimationState since it + // there is absolutely no need for clients of this controller to know + // about these values. + double m_pauseTime; + double m_totalPausedTime; + + // Animations lead dual lives. An active animation will be conceptually owned by + // two controllers, one on the impl thread and one on the main. In reality, there + // will be two separate CCActiveAnimation instances for the same animation. They + // will have the same group id and the same target property (these two values + // uniquely identify an animation). The instance on the impl thread is the instance + // that ultimately controls the values of the animating layer and so we will refer + // to it as the 'controlling instance'. + bool m_isControllingInstance; +}; + +} // namespace WebCore + +#endif // CCActiveAnimation_h diff --git a/cc/CCActiveAnimationTest.cpp b/cc/CCActiveAnimationTest.cpp new file mode 100644 index 0000000..86d4004 --- /dev/null +++ b/cc/CCActiveAnimationTest.cpp @@ -0,0 +1,208 @@ +// Copyright 2012 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 "CCActiveAnimation.h" + +#include "CCAnimationTestCommon.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <wtf/Vector.h> + +using namespace WebKitTests; +using namespace WebCore; + +namespace { + +PassOwnPtr<CCActiveAnimation> createActiveAnimation(int iterations) +{ + OwnPtr<CCActiveAnimation> toReturn(CCActiveAnimation::create(adoptPtr(new FakeFloatAnimationCurve), 0, 1, CCActiveAnimation::Opacity)); + toReturn->setIterations(iterations); + return toReturn.release(); +} + +TEST(CCActiveAnimationTest, TrimTimeZeroIterations) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(0)); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(-1)); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(0)); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(1)); +} + +TEST(CCActiveAnimationTest, TrimTimeOneIteration) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(1)); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(-1)); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(0)); + EXPECT_EQ(1, anim->trimTimeToCurrentIteration(1)); + EXPECT_EQ(1, anim->trimTimeToCurrentIteration(2)); +} + +TEST(CCActiveAnimationTest, TrimTimeInfiniteIterations) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(-1)); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(0)); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(0.5)); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(1)); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(1.5)); +} + +TEST(CCActiveAnimationTest, TrimTimeAlternating) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(-1)); + anim->setAlternatesDirection(true); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(0)); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(0.5)); + EXPECT_EQ(1, anim->trimTimeToCurrentIteration(1)); + EXPECT_EQ(0.75, anim->trimTimeToCurrentIteration(1.25)); +} + +TEST(CCActiveAnimationTest, TrimTimeStartTime) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(1)); + anim->setStartTime(4); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(0)); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(4)); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(4.5)); + EXPECT_EQ(1, anim->trimTimeToCurrentIteration(5)); + EXPECT_EQ(1, anim->trimTimeToCurrentIteration(6)); +} + +TEST(CCActiveAnimationTest, TrimTimeTimeOffset) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(1)); + anim->setTimeOffset(4); + anim->setStartTime(4); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(0)); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(0.5)); + EXPECT_EQ(1, anim->trimTimeToCurrentIteration(1)); + EXPECT_EQ(1, anim->trimTimeToCurrentIteration(1)); +} + +TEST(CCActiveAnimationTest, TrimTimePauseResume) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(1)); + anim->setRunState(CCActiveAnimation::Running, 0); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(0)); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(0.5)); + anim->setRunState(CCActiveAnimation::Paused, 0.5); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(1024)); + anim->setRunState(CCActiveAnimation::Running, 1024); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(1024)); + EXPECT_EQ(1, anim->trimTimeToCurrentIteration(1024.5)); +} + +TEST(CCActiveAnimationTest, TrimTimeSuspendResume) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(1)); + anim->setRunState(CCActiveAnimation::Running, 0); + EXPECT_EQ(0, anim->trimTimeToCurrentIteration(0)); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(0.5)); + anim->suspend(0.5); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(1024)); + anim->resume(1024); + EXPECT_EQ(0.5, anim->trimTimeToCurrentIteration(1024)); + EXPECT_EQ(1, anim->trimTimeToCurrentIteration(1024.5)); +} + +TEST(CCActiveAnimationTest, IsFinishedAtZeroIterations) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(0)); + anim->setRunState(CCActiveAnimation::Running, 0); + EXPECT_FALSE(anim->isFinishedAt(-1)); + EXPECT_TRUE(anim->isFinishedAt(0)); + EXPECT_TRUE(anim->isFinishedAt(1)); +} + +TEST(CCActiveAnimationTest, IsFinishedAtOneIteration) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(1)); + anim->setRunState(CCActiveAnimation::Running, 0); + EXPECT_FALSE(anim->isFinishedAt(-1)); + EXPECT_FALSE(anim->isFinishedAt(0)); + EXPECT_TRUE(anim->isFinishedAt(1)); + EXPECT_TRUE(anim->isFinishedAt(2)); +} + +TEST(CCActiveAnimationTest, IsFinishedAtInfiniteIterations) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(-1)); + anim->setRunState(CCActiveAnimation::Running, 0); + EXPECT_FALSE(anim->isFinishedAt(0)); + EXPECT_FALSE(anim->isFinishedAt(0.5)); + EXPECT_FALSE(anim->isFinishedAt(1)); + EXPECT_FALSE(anim->isFinishedAt(1.5)); +} + +TEST(CCActiveAnimationTest, IsFinishedAtNotRunning) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(0)); + anim->setRunState(CCActiveAnimation::Running, 0); + EXPECT_TRUE(anim->isFinishedAt(0)); + anim->setRunState(CCActiveAnimation::Paused, 0); + EXPECT_FALSE(anim->isFinishedAt(0)); + anim->setRunState(CCActiveAnimation::WaitingForNextTick, 0); + EXPECT_FALSE(anim->isFinishedAt(0)); + anim->setRunState(CCActiveAnimation::WaitingForTargetAvailability, 0); + EXPECT_FALSE(anim->isFinishedAt(0)); + anim->setRunState(CCActiveAnimation::WaitingForStartTime, 0); + EXPECT_FALSE(anim->isFinishedAt(0)); + anim->setRunState(CCActiveAnimation::Finished, 0); + EXPECT_TRUE(anim->isFinishedAt(0)); + anim->setRunState(CCActiveAnimation::Aborted, 0); + EXPECT_TRUE(anim->isFinishedAt(0)); +} + +TEST(CCActiveAnimationTest, IsFinished) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(1)); + anim->setRunState(CCActiveAnimation::Running, 0); + EXPECT_FALSE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::Paused, 0); + EXPECT_FALSE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::WaitingForNextTick, 0); + EXPECT_FALSE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::WaitingForTargetAvailability, 0); + EXPECT_FALSE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::WaitingForStartTime, 0); + EXPECT_FALSE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::Finished, 0); + EXPECT_TRUE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::Aborted, 0); + EXPECT_TRUE(anim->isFinished()); +} + +TEST(CCActiveAnimationTest, IsFinishedNeedsSynchronizedStartTime) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(1)); + anim->setRunState(CCActiveAnimation::Running, 2); + EXPECT_FALSE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::Paused, 2); + EXPECT_FALSE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::WaitingForNextTick, 2); + EXPECT_FALSE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::WaitingForTargetAvailability, 2); + EXPECT_FALSE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::WaitingForStartTime, 2); + EXPECT_FALSE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::Finished, 0); + EXPECT_TRUE(anim->isFinished()); + anim->setRunState(CCActiveAnimation::Aborted, 0); + EXPECT_TRUE(anim->isFinished()); +} + +TEST(CCActiveAnimationTest, RunStateChangesIgnoredWhileSuspended) +{ + OwnPtr<CCActiveAnimation> anim(createActiveAnimation(1)); + anim->suspend(0); + EXPECT_EQ(CCActiveAnimation::Paused, anim->runState()); + anim->setRunState(CCActiveAnimation::Running, 0); + EXPECT_EQ(CCActiveAnimation::Paused, anim->runState()); + anim->resume(0); + anim->setRunState(CCActiveAnimation::Running, 0); + EXPECT_EQ(CCActiveAnimation::Running, anim->runState()); +} + +} // namespace diff --git a/cc/CCActiveGestureAnimation.cpp b/cc/CCActiveGestureAnimation.cpp new file mode 100644 index 0000000..b8542e7 --- /dev/null +++ b/cc/CCActiveGestureAnimation.cpp @@ -0,0 +1,44 @@ +// Copyright 2012 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 "CCActiveGestureAnimation.h" + +#include "CCGestureCurve.h" +#include "TraceEvent.h" + +namespace WebCore { + +PassOwnPtr<CCActiveGestureAnimation> CCActiveGestureAnimation::create(PassOwnPtr<CCGestureCurve> curve, CCGestureCurveTarget* target) +{ + return adoptPtr(new CCActiveGestureAnimation(curve, target)); +} + +CCActiveGestureAnimation::CCActiveGestureAnimation(PassOwnPtr<CCGestureCurve> curve, CCGestureCurveTarget* target) + : m_startTime(0) + , m_waitingForFirstTick(true) + , m_gestureCurve(curve) + , m_gestureCurveTarget(target) +{ + TRACE_EVENT_ASYNC_BEGIN1("input", "GestureAnimation", this, "curve", m_gestureCurve->debugName()); +} + +CCActiveGestureAnimation::~CCActiveGestureAnimation() +{ + TRACE_EVENT_ASYNC_END0("input", "GestureAnimation", this); +} + +bool CCActiveGestureAnimation::animate(double monotonicTime) +{ + if (m_waitingForFirstTick) { + m_startTime = monotonicTime; + m_waitingForFirstTick = false; + } + + // CCGestureCurves used zero-based time, so subtract start-time. + return m_gestureCurve->apply(monotonicTime - m_startTime, m_gestureCurveTarget); +} + +} // namespace WebCore diff --git a/cc/CCActiveGestureAnimation.h b/cc/CCActiveGestureAnimation.h new file mode 100644 index 0000000..57d70a2 --- /dev/null +++ b/cc/CCActiveGestureAnimation.h @@ -0,0 +1,36 @@ +// Copyright 2012 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. + +#ifndef CCActiveGestureAnimation_h +#define CCActiveGestureAnimation_h + +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCGestureCurve; +class CCGestureCurveTarget; + +class CCActiveGestureAnimation { + WTF_MAKE_NONCOPYABLE(CCActiveGestureAnimation); +public: + static PassOwnPtr<CCActiveGestureAnimation> create(PassOwnPtr<CCGestureCurve>, CCGestureCurveTarget*); + ~CCActiveGestureAnimation(); + + bool animate(double monotonicTime); + +private: + CCActiveGestureAnimation(PassOwnPtr<CCGestureCurve>, CCGestureCurveTarget*); + + double m_startTime; + double m_waitingForFirstTick; + OwnPtr<CCGestureCurve> m_gestureCurve; + CCGestureCurveTarget* m_gestureCurveTarget; +}; + +} // namespace WebCore + +#endif diff --git a/cc/CCAnimationCurve.cpp b/cc/CCAnimationCurve.cpp new file mode 100644 index 0000000..b9e80b2 --- /dev/null +++ b/cc/CCAnimationCurve.cpp @@ -0,0 +1,23 @@ +// Copyright 2012 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 "CCAnimationCurve.h" + +namespace WebCore { + +const CCFloatAnimationCurve* CCAnimationCurve::toFloatAnimationCurve() const +{ + ASSERT(type() == CCAnimationCurve::Float); + return static_cast<const CCFloatAnimationCurve*>(this); +} + +const CCTransformAnimationCurve* CCAnimationCurve::toTransformAnimationCurve() const +{ + ASSERT(type() == CCAnimationCurve::Transform); + return static_cast<const CCTransformAnimationCurve*>(this); +} + +} // namespace WebCore diff --git a/cc/CCAnimationCurve.h b/cc/CCAnimationCurve.h new file mode 100644 index 0000000..4fb6141 --- /dev/null +++ b/cc/CCAnimationCurve.h @@ -0,0 +1,56 @@ +// Copyright 2012 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. + +#ifndef CCAnimationCurve_h +#define CCAnimationCurve_h + +#include <public/WebTransformationMatrix.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCFloatAnimationCurve; +class CCTransformAnimationCurve; +class IntSize; +class TransformOperations; + +// An animation curve is a function that returns a value given a time. +// There are currently only two types of curve, float and transform. +class CCAnimationCurve { +public: + enum Type { Float, Transform }; + + virtual ~CCAnimationCurve() { } + + virtual double duration() const = 0; + virtual Type type() const = 0; + virtual PassOwnPtr<CCAnimationCurve> clone() const = 0; + + const CCFloatAnimationCurve* toFloatAnimationCurve() const; + const CCTransformAnimationCurve* toTransformAnimationCurve() const; +}; + +class CCFloatAnimationCurve : public CCAnimationCurve { +public: + virtual ~CCFloatAnimationCurve() { } + + virtual float getValue(double t) const = 0; + + // Partial CCAnimation implementation. + virtual Type type() const OVERRIDE { return Float; } +}; + +class CCTransformAnimationCurve : public CCAnimationCurve { +public: + virtual ~CCTransformAnimationCurve() { } + + virtual WebKit::WebTransformationMatrix getValue(double t) const = 0; + + // Partial CCAnimation implementation. + virtual Type type() const OVERRIDE { return Transform; } +}; + +} // namespace WebCore + +#endif // CCAnimation_h diff --git a/cc/CCAnimationEvents.h b/cc/CCAnimationEvents.h new file mode 100644 index 0000000..b1f5d2a --- /dev/null +++ b/cc/CCAnimationEvents.h @@ -0,0 +1,38 @@ +// Copyright 2012 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. + +#ifndef CCAnimationEvents_h +#define CCAnimationEvents_h + +#include "CCActiveAnimation.h" + +#include <wtf/PassOwnPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +struct CCAnimationEvent { + enum Type { Started, Finished }; + + CCAnimationEvent(Type type, int layerId, int groupId, CCActiveAnimation::TargetProperty targetProperty, double monotonicTime) + : type(type) + , layerId(layerId) + , groupId(groupId) + , targetProperty(targetProperty) + , monotonicTime(monotonicTime) + { + } + + Type type; + int layerId; + int groupId; + CCActiveAnimation::TargetProperty targetProperty; + double monotonicTime; +}; + +typedef Vector<CCAnimationEvent> CCAnimationEventsVector; + +} // namespace WebCore + +#endif // CCAnimationEvents_h diff --git a/cc/CCCheckerboardDrawQuad.cpp b/cc/CCCheckerboardDrawQuad.cpp new file mode 100644 index 0000000..0f8b499 --- /dev/null +++ b/cc/CCCheckerboardDrawQuad.cpp @@ -0,0 +1,28 @@ +// Copyright 2012 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 "CCCheckerboardDrawQuad.h" + +namespace WebCore { + +PassOwnPtr<CCCheckerboardDrawQuad> CCCheckerboardDrawQuad::create(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect) +{ + return adoptPtr(new CCCheckerboardDrawQuad(sharedQuadState, quadRect)); +} + +CCCheckerboardDrawQuad::CCCheckerboardDrawQuad(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect) + : CCDrawQuad(sharedQuadState, CCDrawQuad::Checkerboard, quadRect) +{ +} + +const CCCheckerboardDrawQuad* CCCheckerboardDrawQuad::materialCast(const CCDrawQuad* quad) +{ + ASSERT(quad->material() == CCDrawQuad::Checkerboard); + return static_cast<const CCCheckerboardDrawQuad*>(quad); +} + + +} diff --git a/cc/CCCheckerboardDrawQuad.h b/cc/CCCheckerboardDrawQuad.h new file mode 100644 index 0000000..c1bfe61 --- /dev/null +++ b/cc/CCCheckerboardDrawQuad.h @@ -0,0 +1,28 @@ +// Copyright 2012 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. + +#ifndef CCCheckerboardDrawQuad_h +#define CCCheckerboardDrawQuad_h + +#include "CCDrawQuad.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +#pragma pack(push, 4) + +class CCCheckerboardDrawQuad : public CCDrawQuad { +public: + static PassOwnPtr<CCCheckerboardDrawQuad> create(const CCSharedQuadState*, const IntRect&); + + static const CCCheckerboardDrawQuad* materialCast(const CCDrawQuad*); +private: + CCCheckerboardDrawQuad(const CCSharedQuadState*, const IntRect&); +}; + +#pragma pack(pop) + +} + +#endif diff --git a/cc/CCCompletionEvent.h b/cc/CCCompletionEvent.h new file mode 100644 index 0000000..7445733 --- /dev/null +++ b/cc/CCCompletionEvent.h @@ -0,0 +1,65 @@ +// 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. + +#ifndef CCCompletionEvent_h +#define CCCompletionEvent_h + +#include <wtf/ThreadingPrimitives.h> + +namespace WebCore { + +// Used for making blocking calls from one thread to another. Use only when +// absolutely certain that doing-so will not lead to a deadlock. +// +// It is safe to destroy this object as soon as wait() returns. +class CCCompletionEvent { +public: + CCCompletionEvent() + { +#ifndef NDEBUG + m_waited = false; + m_signaled = false; +#endif + m_mutex.lock(); + } + + ~CCCompletionEvent() + { + m_mutex.unlock(); + ASSERT(m_waited); + ASSERT(m_signaled); + } + + void wait() + { + ASSERT(!m_waited); +#ifndef NDEBUG + m_waited = true; +#endif + m_condition.wait(m_mutex); + } + + void signal() + { + MutexLocker lock(m_mutex); + ASSERT(!m_signaled); +#ifndef NDEBUG + m_signaled = true; +#endif + m_condition.signal(); + } + +private: + Mutex m_mutex; + ThreadCondition m_condition; +#ifndef NDEBUG + // Used to assert that wait() and signal() are each called exactly once. + bool m_waited; + bool m_signaled; +#endif +}; + +} + +#endif diff --git a/cc/CCDamageTracker.cpp b/cc/CCDamageTracker.cpp new file mode 100644 index 0000000..90d6e29 --- /dev/null +++ b/cc/CCDamageTracker.cpp @@ -0,0 +1,345 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCDamageTracker.h" + +#include "CCLayerImpl.h" +#include "CCLayerTreeHostCommon.h" +#include "CCMathUtil.h" +#include "CCRenderSurface.h" +#include <public/WebFilterOperations.h> + +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +PassOwnPtr<CCDamageTracker> CCDamageTracker::create() +{ + return adoptPtr(new CCDamageTracker()); +} + +CCDamageTracker::CCDamageTracker() + : m_forceFullDamageNextUpdate(false) +{ + m_currentRectHistory = adoptPtr(new RectMap); + m_nextRectHistory = adoptPtr(new RectMap); +} + +CCDamageTracker::~CCDamageTracker() +{ +} + +static inline void expandRectWithFilters(FloatRect& rect, const WebKit::WebFilterOperations& filters) +{ + int top, right, bottom, left; + filters.getOutsets(top, right, bottom, left); + rect.move(-left, -top); + rect.expand(left + right, top + bottom); +} + +static inline void expandDamageRectInsideRectWithFilters(FloatRect& damageRect, const FloatRect& preFilterRect, const WebKit::WebFilterOperations& filters) +{ + FloatRect expandedDamageRect = damageRect; + expandRectWithFilters(expandedDamageRect, filters); + FloatRect filterRect = preFilterRect; + expandRectWithFilters(filterRect, filters); + + expandedDamageRect.intersect(filterRect); + damageRect.unite(expandedDamageRect); +} + +void CCDamageTracker::updateDamageTrackingState(const Vector<CCLayerImpl*>& layerList, int targetSurfaceLayerID, bool targetSurfacePropertyChangedOnlyFromDescendant, const IntRect& targetSurfaceContentRect, CCLayerImpl* targetSurfaceMaskLayer, const WebKit::WebFilterOperations& filters) +{ + // + // This function computes the "damage rect" of a target surface, and updates the state + // that is used to correctly track damage across frames. The damage rect is the region + // of the surface that may have changed and needs to be redrawn. This can be used to + // scissor what is actually drawn, to save GPU computation and bandwidth. + // + // The surface's damage rect is computed as the union of all possible changes that + // have happened to the surface since the last frame was drawn. This includes: + // - any changes for existing layers/surfaces that contribute to the target surface + // - layers/surfaces that existed in the previous frame, but no longer exist. + // + // The basic algorithm for computing the damage region is as follows: + // + // 1. compute damage caused by changes in active/new layers + // for each layer in the layerList: + // if the layer is actually a renderSurface: + // add the surface's damage to our target surface. + // else + // add the layer's damage to the target surface. + // + // 2. compute damage caused by the target surface's mask, if it exists. + // + // 3. compute damage caused by old layers/surfaces that no longer exist + // for each leftover layer: + // add the old layer/surface bounds to the target surface damage. + // + // 4. combine all partial damage rects to get the full damage rect. + // + // Additional important points: + // + // - This algorithm is implicitly recursive; it assumes that descendant surfaces have + // already computed their damage. + // + // - Changes to layers/surfaces indicate "damage" to the target surface; If a layer is + // not changed, it does NOT mean that the layer can skip drawing. All layers that + // overlap the damaged region still need to be drawn. For example, if a layer + // changed its opacity, then layers underneath must be re-drawn as well, even if + // they did not change. + // + // - If a layer/surface property changed, the old bounds and new bounds may overlap... + // i.e. some of the exposed region may not actually be exposing anything. But this + // does not artificially inflate the damage rect. If the layer changed, its entire + // old bounds would always need to be redrawn, regardless of how much it overlaps + // with the layer's new bounds, which also need to be entirely redrawn. + // + // - See comments in the rest of the code to see what exactly is considered a "change" + // in a layer/surface. + // + // - To correctly manage exposed rects, two RectMaps are maintained: + // + // 1. The "current" map contains all the layer bounds that contributed to the + // previous frame (even outside the previous damaged area). If a layer changes + // or does not exist anymore, those regions are then exposed and damage the + // target surface. As the algorithm progresses, entries are removed from the + // map until it has only leftover layers that no longer exist. + // + // 2. The "next" map starts out empty, and as the algorithm progresses, every + // layer/surface that contributes to the surface is added to the map. + // + // 3. After the damage rect is computed, the two maps are swapped, so that the + // damage tracker is ready for the next frame. + // + + // These functions cannot be bypassed with early-exits, even if we know what the + // damage will be for this frame, because we need to update the damage tracker state + // to correctly track the next frame. + FloatRect damageFromActiveLayers = trackDamageFromActiveLayers(layerList, targetSurfaceLayerID); + FloatRect damageFromSurfaceMask = trackDamageFromSurfaceMask(targetSurfaceMaskLayer); + FloatRect damageFromLeftoverRects = trackDamageFromLeftoverRects(); + + FloatRect damageRectForThisUpdate; + + if (m_forceFullDamageNextUpdate || targetSurfacePropertyChangedOnlyFromDescendant) { + damageRectForThisUpdate = targetSurfaceContentRect; + m_forceFullDamageNextUpdate = false; + } else { + // FIXME: can we clamp this damage to the surface's content rect? (affects performance, but not correctness) + damageRectForThisUpdate = damageFromActiveLayers; + damageRectForThisUpdate.uniteIfNonZero(damageFromSurfaceMask); + damageRectForThisUpdate.uniteIfNonZero(damageFromLeftoverRects); + + if (filters.hasFilterThatMovesPixels()) + expandRectWithFilters(damageRectForThisUpdate, filters); + } + + // Damage accumulates until we are notified that we actually did draw on that frame. + m_currentDamageRect.uniteIfNonZero(damageRectForThisUpdate); + + // The next history map becomes the current map for the next frame. Note this must + // happen every frame to correctly track changes, even if damage accumulates over + // multiple frames before actually being drawn. + swap(m_currentRectHistory, m_nextRectHistory); +} + +FloatRect CCDamageTracker::removeRectFromCurrentFrame(int layerID, bool& layerIsNew) +{ + layerIsNew = !m_currentRectHistory->contains(layerID); + + // take() will remove the entry from the map, or if not found, return a default (empty) rect. + return m_currentRectHistory->take(layerID); +} + +void CCDamageTracker::saveRectForNextFrame(int layerID, const FloatRect& targetSpaceRect) +{ + // This layer should not yet exist in next frame's history. + ASSERT(layerID > 0); + ASSERT(m_nextRectHistory->find(layerID) == m_nextRectHistory->end()); + m_nextRectHistory->set(layerID, targetSpaceRect); +} + +FloatRect CCDamageTracker::trackDamageFromActiveLayers(const Vector<CCLayerImpl*>& layerList, int targetSurfaceLayerID) +{ + FloatRect damageRect = FloatRect(); + + for (unsigned layerIndex = 0; layerIndex < layerList.size(); ++layerIndex) { + // Visit layers in back-to-front order. + CCLayerImpl* layer = layerList[layerIndex]; + + if (CCLayerTreeHostCommon::renderSurfaceContributesToTarget<CCLayerImpl>(layer, targetSurfaceLayerID)) + extendDamageForRenderSurface(layer, damageRect); + else + extendDamageForLayer(layer, damageRect); + } + + return damageRect; +} + +FloatRect CCDamageTracker::trackDamageFromSurfaceMask(CCLayerImpl* targetSurfaceMaskLayer) +{ + FloatRect damageRect = FloatRect(); + + if (!targetSurfaceMaskLayer) + return damageRect; + + // Currently, if there is any change to the mask, we choose to damage the entire + // surface. This could potentially be optimized later, but it is not expected to be a + // common case. + if (targetSurfaceMaskLayer->layerPropertyChanged() || !targetSurfaceMaskLayer->updateRect().isEmpty()) + damageRect = FloatRect(FloatPoint::zero(), FloatSize(targetSurfaceMaskLayer->bounds())); + + return damageRect; +} + +FloatRect CCDamageTracker::trackDamageFromLeftoverRects() +{ + // After computing damage for all active layers, any leftover items in the current + // rect history correspond to layers/surfaces that no longer exist. So, these regions + // are now exposed on the target surface. + + FloatRect damageRect = FloatRect(); + + for (RectMap::iterator it = m_currentRectHistory->begin(); it != m_currentRectHistory->end(); ++it) + damageRect.unite(it->second); + + m_currentRectHistory->clear(); + + return damageRect; +} + +static bool layerNeedsToRedrawOntoItsTargetSurface(CCLayerImpl* layer) +{ + // If the layer does NOT own a surface but has SurfacePropertyChanged, + // this means that its target surface is affected and needs to be redrawn. + // However, if the layer DOES own a surface, then the SurfacePropertyChanged + // flag should not be used here, because that flag represents whether the + // layer's surface has changed. + if (layer->renderSurface()) + return layer->layerPropertyChanged(); + return layer->layerPropertyChanged() || layer->layerSurfacePropertyChanged(); +} + +void CCDamageTracker::extendDamageForLayer(CCLayerImpl* layer, FloatRect& targetDamageRect) +{ + // There are two ways that a layer can damage a region of the target surface: + // 1. Property change (e.g. opacity, position, transforms): + // - the entire region of the layer itself damages the surface. + // - the old layer region also damages the surface, because this region is now exposed. + // - note that in many cases the old and new layer rects may overlap, which is fine. + // + // 2. Repaint/update: If a region of the layer that was repainted/updated, that + // region damages the surface. + // + // Property changes take priority over update rects. + // + // This method is called when we want to consider how a layer contributes to its + // targetRenderSurface, even if that layer owns the targetRenderSurface itself. + // To consider how a layer's targetSurface contributes to the ancestorSurface, + // extendDamageForRenderSurface() must be called instead. + + bool layerIsNew = false; + FloatRect oldRectInTargetSpace = removeRectFromCurrentFrame(layer->id(), layerIsNew); + + FloatRect rectInTargetSpace = CCMathUtil::mapClippedRect(layer->drawTransform(), FloatRect(FloatPoint::zero(), layer->contentBounds())); + saveRectForNextFrame(layer->id(), rectInTargetSpace); + + if (layerIsNew || layerNeedsToRedrawOntoItsTargetSurface(layer)) { + // If a layer is new or has changed, then its entire layer rect affects the target surface. + targetDamageRect.uniteIfNonZero(rectInTargetSpace); + + // The layer's old region is now exposed on the target surface, too. + // Note oldRectInTargetSpace is already in target space. + targetDamageRect.uniteIfNonZero(oldRectInTargetSpace); + } else if (!layer->updateRect().isEmpty()) { + // If the layer properties havent changed, then the the target surface is only + // affected by the layer's update area, which could be empty. + FloatRect updateContentRect = layer->updateRect(); + float widthScale = layer->contentBounds().width() / static_cast<float>(layer->bounds().width()); + float heightScale = layer->contentBounds().height() / static_cast<float>(layer->bounds().height()); + updateContentRect.scale(widthScale, heightScale); + + FloatRect updateRectInTargetSpace = CCMathUtil::mapClippedRect(layer->drawTransform(), updateContentRect); + targetDamageRect.uniteIfNonZero(updateRectInTargetSpace); + } +} + +void CCDamageTracker::extendDamageForRenderSurface(CCLayerImpl* layer, FloatRect& targetDamageRect) +{ + // There are two ways a "descendant surface" can damage regions of the "target surface": + // 1. Property change: + // - a surface's geometry can change because of + // - changes to descendants (i.e. the subtree) that affect the surface's content rect + // - changes to ancestor layers that propagate their property changes to their entire subtree. + // - just like layers, both the old surface rect and new surface rect will + // damage the target surface in this case. + // + // 2. Damage rect: This surface may have been damaged by its own layerList as well, and that damage + // should propagate to the target surface. + // + + CCRenderSurface* renderSurface = layer->renderSurface(); + + bool surfaceIsNew = false; + FloatRect oldSurfaceRect = removeRectFromCurrentFrame(layer->id(), surfaceIsNew); + + FloatRect surfaceRectInTargetSpace = renderSurface->drawableContentRect(); // already includes replica if it exists. + saveRectForNextFrame(layer->id(), surfaceRectInTargetSpace); + + FloatRect damageRectInLocalSpace; + if (surfaceIsNew || renderSurface->surfacePropertyChanged() || layer->layerSurfacePropertyChanged()) { + // The entire surface contributes damage. + damageRectInLocalSpace = renderSurface->contentRect(); + + // The surface's old region is now exposed on the target surface, too. + targetDamageRect.uniteIfNonZero(oldSurfaceRect); + } else { + // Only the surface's damageRect will damage the target surface. + damageRectInLocalSpace = renderSurface->damageTracker()->currentDamageRect(); + } + + // If there was damage, transform it to target space, and possibly contribute its reflection if needed. + if (!damageRectInLocalSpace.isEmpty()) { + const WebTransformationMatrix& drawTransform = renderSurface->drawTransform(); + FloatRect damageRectInTargetSpace = CCMathUtil::mapClippedRect(drawTransform, damageRectInLocalSpace); + targetDamageRect.uniteIfNonZero(damageRectInTargetSpace); + + if (layer->replicaLayer()) { + const WebTransformationMatrix& replicaDrawTransform = renderSurface->replicaDrawTransform(); + targetDamageRect.uniteIfNonZero(CCMathUtil::mapClippedRect(replicaDrawTransform, damageRectInLocalSpace)); + } + } + + // If there was damage on the replica's mask, then the target surface receives that damage as well. + if (layer->replicaLayer() && layer->replicaLayer()->maskLayer()) { + CCLayerImpl* replicaMaskLayer = layer->replicaLayer()->maskLayer(); + + bool replicaIsNew = false; + removeRectFromCurrentFrame(replicaMaskLayer->id(), replicaIsNew); + + const WebTransformationMatrix& replicaDrawTransform = renderSurface->replicaDrawTransform(); + FloatRect replicaMaskLayerRect = CCMathUtil::mapClippedRect(replicaDrawTransform, FloatRect(FloatPoint::zero(), FloatSize(replicaMaskLayer->bounds().width(), replicaMaskLayer->bounds().height()))); + saveRectForNextFrame(replicaMaskLayer->id(), replicaMaskLayerRect); + + // In the current implementation, a change in the replica mask damages the entire replica region. + if (replicaIsNew || replicaMaskLayer->layerPropertyChanged() || !replicaMaskLayer->updateRect().isEmpty()) + targetDamageRect.uniteIfNonZero(replicaMaskLayerRect); + } + + // If the layer has a background filter, this may cause pixels in our surface to be expanded, so we will need to expand any damage + // at or below this layer. We expand the damage from this layer too, as we need to readback those pixels from the surface with only + // the contents of layers below this one in them. This means we need to redraw any pixels in the surface being used for the blur in + // this layer this frame. + if (layer->backgroundFilters().hasFilterThatMovesPixels()) + expandDamageRectInsideRectWithFilters(targetDamageRect, surfaceRectInTargetSpace, layer->backgroundFilters()); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCDamageTracker.h b/cc/CCDamageTracker.h new file mode 100644 index 0000000..eb2a644 --- /dev/null +++ b/cc/CCDamageTracker.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef CCDamageTracker_h +#define CCDamageTracker_h + +#include "FloatRect.h" +#include <wtf/HashMap.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/Vector.h> + +namespace WebKit { +class WebFilterOperations; +} + +namespace WebCore { + +class CCLayerImpl; +class CCRenderSurface; + +// Computes the region where pixels have actually changed on a RenderSurface. This region is used +// to scissor what is actually drawn to the screen to save GPU computation and bandwidth. +class CCDamageTracker { +public: + static PassOwnPtr<CCDamageTracker> create(); + ~CCDamageTracker(); + + void didDrawDamagedArea() { m_currentDamageRect = FloatRect(); } + void forceFullDamageNextUpdate() { m_forceFullDamageNextUpdate = true; } + void updateDamageTrackingState(const Vector<CCLayerImpl*>& layerList, int targetSurfaceLayerID, bool targetSurfacePropertyChangedOnlyFromDescendant, const IntRect& targetSurfaceContentRect, CCLayerImpl* targetSurfaceMaskLayer, const WebKit::WebFilterOperations&); + + const FloatRect& currentDamageRect() { return m_currentDamageRect; } + +private: + CCDamageTracker(); + + FloatRect trackDamageFromActiveLayers(const Vector<CCLayerImpl*>& layerList, int targetSurfaceLayerID); + FloatRect trackDamageFromSurfaceMask(CCLayerImpl* targetSurfaceMaskLayer); + FloatRect trackDamageFromLeftoverRects(); + + FloatRect removeRectFromCurrentFrame(int layerID, bool& layerIsNew); + void saveRectForNextFrame(int layerID, const FloatRect& targetSpaceRect); + + // These helper functions are used only in trackDamageFromActiveLayers(). + void extendDamageForLayer(CCLayerImpl*, FloatRect& targetDamageRect); + void extendDamageForRenderSurface(CCLayerImpl*, FloatRect& targetDamageRect); + + // To correctly track exposed regions, two hashtables of rects are maintained. + // The "current" map is used to compute exposed regions of the current frame, while + // the "next" map is used to collect layer rects that are used in the next frame. + typedef HashMap<int, FloatRect> RectMap; + OwnPtr<RectMap> m_currentRectHistory; + OwnPtr<RectMap> m_nextRectHistory; + + FloatRect m_currentDamageRect; + bool m_forceFullDamageNextUpdate; +}; + +} // namespace WebCore + +#endif // CCDamageTracker_h diff --git a/cc/CCDamageTrackerTest.cpp b/cc/CCDamageTrackerTest.cpp new file mode 100644 index 0000000..cc1d0ba --- /dev/null +++ b/cc/CCDamageTrackerTest.cpp @@ -0,0 +1,1138 @@ +// 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 "config.h" + +#include "CCDamageTracker.h" + +#include "CCLayerImpl.h" +#include "CCLayerSorter.h" +#include "CCLayerTreeHostCommon.h" +#include "CCLayerTreeTestCommon.h" +#include "CCMathUtil.h" +#include "CCSingleThreadProxy.h" +#include <gtest/gtest.h> +#include <public/WebFilterOperation.h> +#include <public/WebFilterOperations.h> + +using namespace WebCore; +using namespace WebKit; +using namespace WTF; +using namespace WebKitTests; + +namespace { + +void executeCalculateDrawTransformsAndVisibility(CCLayerImpl* root, Vector<CCLayerImpl*>& renderSurfaceLayerList) +{ + CCLayerSorter layerSorter; + int dummyMaxTextureSize = 512; + + // Sanity check: The test itself should create the root layer's render surface, so + // that the surface (and its damage tracker) can persist across multiple + // calls to this function. + ASSERT_TRUE(root->renderSurface()); + ASSERT_FALSE(renderSurfaceLayerList.size()); + + CCLayerTreeHostCommon::calculateDrawTransforms(root, root->bounds(), 1, &layerSorter, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); +} + +void clearDamageForAllSurfaces(CCLayerImpl* layer) +{ + if (layer->renderSurface()) + layer->renderSurface()->damageTracker()->didDrawDamagedArea(); + + // Recursively clear damage for any existing surface. + for (size_t i = 0; i < layer->children().size(); ++i) + clearDamageForAllSurfaces(layer->children()[i].get()); +} + +void emulateDrawingOneFrame(CCLayerImpl* root) +{ + // This emulates only the steps that are relevant to testing the damage tracker: + // 1. computing the render passes and layerlists + // 2. updating all damage trackers in the correct order + // 3. resetting all updateRects and propertyChanged flags for all layers and surfaces. + + Vector<CCLayerImpl*> renderSurfaceLayerList; + executeCalculateDrawTransformsAndVisibility(root, renderSurfaceLayerList); + + // Iterate back-to-front, so that damage correctly propagates from descendant surfaces to ancestors. + for (int i = renderSurfaceLayerList.size() - 1; i >= 0; --i) { + CCRenderSurface* targetSurface = renderSurfaceLayerList[i]->renderSurface(); + targetSurface->damageTracker()->updateDamageTrackingState(targetSurface->layerList(), targetSurface->owningLayerId(), targetSurface->surfacePropertyChangedOnlyFromDescendant(), targetSurface->contentRect(), renderSurfaceLayerList[i]->maskLayer(), renderSurfaceLayerList[i]->filters()); + } + + root->resetAllChangeTrackingForSubtree(); +} + +PassOwnPtr<CCLayerImpl> createTestTreeWithOneSurface() +{ + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + OwnPtr<CCLayerImpl> child = CCLayerImpl::create(2); + + root->setPosition(FloatPoint::zero()); + root->setAnchorPoint(FloatPoint::zero()); + root->setBounds(IntSize(500, 500)); + root->setContentBounds(IntSize(500, 500)); + root->setDrawsContent(true); + root->createRenderSurface(); + root->renderSurface()->setContentRect(IntRect(IntPoint(), IntSize(500, 500))); + + child->setPosition(FloatPoint(100, 100)); + child->setAnchorPoint(FloatPoint::zero()); + child->setBounds(IntSize(30, 30)); + child->setContentBounds(IntSize(30, 30)); + child->setDrawsContent(true); + root->addChild(child.release()); + + return root.release(); +} + +PassOwnPtr<CCLayerImpl> createTestTreeWithTwoSurfaces() +{ + // This test tree has two render surfaces: one for the root, and one for + // child1. Additionally, the root has a second child layer, and child1 has two + // children of its own. + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + OwnPtr<CCLayerImpl> child1 = CCLayerImpl::create(2); + OwnPtr<CCLayerImpl> child2 = CCLayerImpl::create(3); + OwnPtr<CCLayerImpl> grandChild1 = CCLayerImpl::create(4); + OwnPtr<CCLayerImpl> grandChild2 = CCLayerImpl::create(5); + + root->setPosition(FloatPoint::zero()); + root->setAnchorPoint(FloatPoint::zero()); + root->setBounds(IntSize(500, 500)); + root->setContentBounds(IntSize(500, 500)); + root->setDrawsContent(true); + root->createRenderSurface(); + root->renderSurface()->setContentRect(IntRect(IntPoint(), IntSize(500, 500))); + + child1->setPosition(FloatPoint(100, 100)); + child1->setAnchorPoint(FloatPoint::zero()); + child1->setBounds(IntSize(30, 30)); + child1->setContentBounds(IntSize(30, 30)); + child1->setOpacity(0.5); // with a child that drawsContent, this will cause the layer to create its own renderSurface. + child1->setDrawsContent(false); // this layer does not draw, but is intended to create its own renderSurface. + + child2->setPosition(FloatPoint(11, 11)); + child2->setAnchorPoint(FloatPoint::zero()); + child2->setBounds(IntSize(18, 18)); + child2->setContentBounds(IntSize(18, 18)); + child2->setDrawsContent(true); + + grandChild1->setPosition(FloatPoint(200, 200)); + grandChild1->setAnchorPoint(FloatPoint::zero()); + grandChild1->setBounds(IntSize(6, 8)); + grandChild1->setContentBounds(IntSize(6, 8)); + grandChild1->setDrawsContent(true); + + grandChild2->setPosition(FloatPoint(190, 190)); + grandChild2->setAnchorPoint(FloatPoint::zero()); + grandChild2->setBounds(IntSize(6, 8)); + grandChild2->setContentBounds(IntSize(6, 8)); + grandChild2->setDrawsContent(true); + + child1->addChild(grandChild1.release()); + child1->addChild(grandChild2.release()); + root->addChild(child1.release()); + root->addChild(child2.release()); + + return root.release(); +} + +PassOwnPtr<CCLayerImpl> createAndSetUpTestTreeWithOneSurface() +{ + OwnPtr<CCLayerImpl> root = createTestTreeWithOneSurface(); + + // Setup includes going past the first frame which always damages everything, so + // that we can actually perform specific tests. + emulateDrawingOneFrame(root.get()); + + return root.release(); +} + +PassOwnPtr<CCLayerImpl> createAndSetUpTestTreeWithTwoSurfaces() +{ + OwnPtr<CCLayerImpl> root = createTestTreeWithTwoSurfaces(); + + // Setup includes going past the first frame which always damages everything, so + // that we can actually perform specific tests. + emulateDrawingOneFrame(root.get()); + + return root.release(); +} + +class CCDamageTrackerTest : public testing::Test { +private: + // For testing purposes, fake that we are on the impl thread. + DebugScopedSetImplThread setImplThread; +}; + +TEST_F(CCDamageTrackerTest, sanityCheckTestTreeWithOneSurface) +{ + // Sanity check that the simple test tree will actually produce the expected render + // surfaces and layer lists. + + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + + EXPECT_EQ(2u, root->renderSurface()->layerList().size()); + EXPECT_EQ(1, root->renderSurface()->layerList()[0]->id()); + EXPECT_EQ(2, root->renderSurface()->layerList()[1]->id()); + + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 500, 500), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, sanityCheckTestTreeWithTwoSurfaces) +{ + // Sanity check that the complex test tree will actually produce the expected render + // surfaces and layer lists. + + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + + CCLayerImpl* child1 = root->children()[0].get(); + CCLayerImpl* child2 = root->children()[1].get(); + FloatRect childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + + ASSERT_TRUE(child1->renderSurface()); + EXPECT_FALSE(child2->renderSurface()); + EXPECT_EQ(3u, root->renderSurface()->layerList().size()); + EXPECT_EQ(2u, child1->renderSurface()->layerList().size()); + + // The render surface for child1 only has a contentRect that encloses grandChild1 and grandChild2, because child1 does not draw content. + EXPECT_FLOAT_RECT_EQ(FloatRect(190, 190, 16, 18), childDamageRect); + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 500, 500), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForUpdateRects) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + CCLayerImpl* child = root->children()[0].get(); + + // CASE 1: Setting the update rect should cause the corresponding damage to the surface. + // + clearDamageForAllSurfaces(root.get()); + child->setUpdateRect(FloatRect(10, 11, 12, 13)); + emulateDrawingOneFrame(root.get()); + + // Damage position on the surface should be: position of updateRect (10, 11) relative to the child (100, 100). + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(110, 111, 12, 13), rootDamageRect); + + // CASE 2: The same update rect twice in a row still produces the same damage. + // + clearDamageForAllSurfaces(root.get()); + child->setUpdateRect(FloatRect(10, 11, 12, 13)); + emulateDrawingOneFrame(root.get()); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(110, 111, 12, 13), rootDamageRect); + + // CASE 3: Setting a different update rect should cause damage on the new update region, but no additional exposed old region. + // + clearDamageForAllSurfaces(root.get()); + child->setUpdateRect(FloatRect(20, 25, 1, 2)); + emulateDrawingOneFrame(root.get()); + + // Damage position on the surface should be: position of updateRect (20, 25) relative to the child (100, 100). + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(120, 125, 1, 2), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForPropertyChanges) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + CCLayerImpl* child = root->children()[0].get(); + + // CASE 1: The layer's property changed flag takes priority over update rect. + // + clearDamageForAllSurfaces(root.get()); + child->setUpdateRect(FloatRect(10, 11, 12, 13)); + child->setOpacity(0.5); + emulateDrawingOneFrame(root.get()); + + // Sanity check - we should not have accidentally created a separate render surface for the translucent layer. + ASSERT_FALSE(child->renderSurface()); + ASSERT_EQ(2u, root->renderSurface()->layerList().size()); + + // Damage should be the entire child layer in targetSurface space. + FloatRect expectedRect = FloatRect(100, 100, 30, 30); + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(expectedRect, rootDamageRect); + + // CASE 2: If a layer moves due to property change, it damages both the new location + // and the old (exposed) location. The old location is the entire old layer, + // not just the updateRect. + + // Cycle one frame of no change, just to sanity check that the next rect is not because of the old damage state. + clearDamageForAllSurfaces(root.get()); + emulateDrawingOneFrame(root.get()); + EXPECT_TRUE(root->renderSurface()->damageTracker()->currentDamageRect().isEmpty()); + + // Then, test the actual layer movement. + clearDamageForAllSurfaces(root.get()); + child->setPosition(FloatPoint(200, 230)); + emulateDrawingOneFrame(root.get()); + + // Expect damage to be the combination of the previous one and the new one. + expectedRect.uniteIfNonZero(FloatRect(200, 230, 30, 30)); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(expectedRect, rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForTransformedLayer) +{ + // If a layer is transformed, the damage rect should still enclose the entire + // transformed layer. + + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + CCLayerImpl* child = root->children()[0].get(); + + WebTransformationMatrix rotation; + rotation.rotate(45); + + clearDamageForAllSurfaces(root.get()); + child->setAnchorPoint(FloatPoint(0.5, 0.5)); + child->setPosition(FloatPoint(85, 85)); + emulateDrawingOneFrame(root.get()); + + // Sanity check that the layer actually moved to (85, 85), damaging its old location and new location. + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(85, 85, 45, 45), rootDamageRect); + + // With the anchor on the layer's center, now we can test the rotation more + // intuitively, since it applies about the layer's anchor. + clearDamageForAllSurfaces(root.get()); + child->setTransform(rotation); + emulateDrawingOneFrame(root.get()); + + // Since the child layer is square, rotation by 45 degrees about the center should + // increase the size of the expected rect by sqrt(2), centered around (100, 100). The + // old exposed region should be fully contained in the new region. + double expectedWidth = 30 * sqrt(2.0); + double expectedPosition = 100 - 0.5 * expectedWidth; + FloatRect expectedRect(expectedPosition, expectedPosition, expectedWidth, expectedWidth); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(expectedRect, rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForPerspectiveClippedLayer) +{ + // If a layer has a perspective transform that causes w < 0, then not clipping the + // layer can cause an invalid damage rect. This test checks that the w < 0 case is + // tracked properly. + // + // The transform is constructed so that if w < 0 clipping is not performed, the + // incorrect rect will be very small, specifically: position (500.972504, 498.544617) and size 0.056610 x 2.910767. + // Instead, the correctly transformed rect should actually be very huge (i.e. in theory, -infinity on the left), + // and positioned so that the right-most bound rect will be approximately 501 units in root surface space. + // + + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + CCLayerImpl* child = root->children()[0].get(); + + WebTransformationMatrix transform; + transform.translate3d(500, 500, 0); + transform.applyPerspective(1); + transform.rotate3d(0, 45, 0); + transform.translate3d(-50, -50, 0); + + // Set up the child + child->setPosition(FloatPoint(0, 0)); + child->setBounds(IntSize(100, 100)); + child->setContentBounds(IntSize(100, 100)); + child->setTransform(transform); + emulateDrawingOneFrame(root.get()); + + // Sanity check that the child layer's bounds would actually get clipped by w < 0, + // otherwise this test is not actually testing the intended scenario. + FloatQuad testQuad(FloatRect(FloatPoint::zero(), FloatSize(100, 100))); + bool clipped = false; + CCMathUtil::mapQuad(transform, testQuad, clipped); + EXPECT_TRUE(clipped); + + // Damage the child without moving it. + clearDamageForAllSurfaces(root.get()); + child->setOpacity(0.5); + emulateDrawingOneFrame(root.get()); + + // The expected damage should cover the entire root surface (500x500), but we don't + // care whether the damage rect was clamped or is larger than the surface for this test. + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + FloatRect damageWeCareAbout = FloatRect(FloatPoint::zero(), FloatSize(500, 500)); + EXPECT_TRUE(rootDamageRect.contains(damageWeCareAbout)); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForBlurredSurface) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + CCLayerImpl* child = root->children()[0].get(); + + WebFilterOperations filters; + filters.append(WebFilterOperation::createBlurFilter(5)); + int outsetTop, outsetRight, outsetBottom, outsetLeft; + filters.getOutsets(outsetTop, outsetRight, outsetBottom, outsetLeft); + + // Setting the filter will damage the whole surface. + clearDamageForAllSurfaces(root.get()); + root->setFilters(filters); + emulateDrawingOneFrame(root.get()); + + // Setting the update rect should cause the corresponding damage to the surface, blurred based on the size of the blur filter. + clearDamageForAllSurfaces(root.get()); + child->setUpdateRect(FloatRect(10, 11, 12, 13)); + emulateDrawingOneFrame(root.get()); + + // Damage position on the surface should be: position of updateRect (10, 11) relative to the child (100, 100), but expanded by the blur outsets. + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + FloatRect expectedDamageRect = FloatRect(110, 111, 12, 13); + expectedDamageRect.move(-outsetLeft, -outsetTop); + expectedDamageRect.expand(outsetLeft + outsetRight, outsetTop + outsetBottom); + EXPECT_FLOAT_RECT_EQ(expectedDamageRect, rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForBackgroundBlurredChild) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + CCLayerImpl* child1 = root->children()[0].get(); + CCLayerImpl* child2 = root->children()[1].get(); + + // Allow us to set damage on child1 too. + child1->setDrawsContent(true); + + WebFilterOperations filters; + filters.append(WebFilterOperation::createBlurFilter(2)); + int outsetTop, outsetRight, outsetBottom, outsetLeft; + filters.getOutsets(outsetTop, outsetRight, outsetBottom, outsetLeft); + + // Setting the filter will damage the whole surface. + clearDamageForAllSurfaces(root.get()); + child1->setBackgroundFilters(filters); + emulateDrawingOneFrame(root.get()); + + // CASE 1: Setting the update rect should cause the corresponding damage to + // the surface, blurred based on the size of the child's background blur + // filter. + clearDamageForAllSurfaces(root.get()); + root->setUpdateRect(FloatRect(297, 297, 2, 2)); + emulateDrawingOneFrame(root.get()); + + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + // Damage position on the surface should be a composition of the damage on the root and on child2. + // Damage on the root should be: position of updateRect (297, 297), but expanded by the blur outsets. + FloatRect expectedDamageRect = FloatRect(297, 297, 2, 2); + expectedDamageRect.move(-outsetLeft, -outsetTop); + expectedDamageRect.expand(outsetLeft + outsetRight, outsetTop + outsetBottom); + EXPECT_FLOAT_RECT_EQ(expectedDamageRect, rootDamageRect); + + // CASE 2: Setting the update rect should cause the corresponding damage to + // the surface, blurred based on the size of the child's background blur + // filter. Since the damage extends to the right/bottom outside of the + // blurred layer, only the left/top should end up expanded. + clearDamageForAllSurfaces(root.get()); + root->setUpdateRect(FloatRect(297, 297, 30, 30)); + emulateDrawingOneFrame(root.get()); + + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + // Damage position on the surface should be a composition of the damage on the root and on child2. + // Damage on the root should be: position of updateRect (297, 297), but expanded on the left/top + // by the blur outsets. + expectedDamageRect = FloatRect(297, 297, 30, 30); + expectedDamageRect.move(-outsetLeft, -outsetTop); + expectedDamageRect.expand(outsetLeft, outsetTop); + EXPECT_FLOAT_RECT_EQ(expectedDamageRect, rootDamageRect); + + // CASE 3: Setting this update rect outside the blurred contentBounds of the blurred + // child1 will not cause it to be expanded. + clearDamageForAllSurfaces(root.get()); + root->setUpdateRect(FloatRect(30, 30, 2, 2)); + emulateDrawingOneFrame(root.get()); + + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + // Damage on the root should be: position of updateRect (30, 30), not + // expanded. + expectedDamageRect = FloatRect(30, 30, 2, 2); + EXPECT_FLOAT_RECT_EQ(expectedDamageRect, rootDamageRect); + + // CASE 4: Setting this update rect inside the blurred contentBounds but outside the + // original contentBounds of the blurred child1 will cause it to be expanded. + clearDamageForAllSurfaces(root.get()); + root->setUpdateRect(FloatRect(99, 99, 1, 1)); + emulateDrawingOneFrame(root.get()); + + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + // Damage on the root should be: position of updateRect (99, 99), expanded + // by the blurring on child1, but since it is 1 pixel outside the layer, the + // expanding should be reduced by 1. + expectedDamageRect = FloatRect(99, 99, 1, 1); + expectedDamageRect.move(-outsetLeft + 1, -outsetTop + 1); + expectedDamageRect.expand(outsetLeft + outsetRight - 1, outsetTop + outsetBottom - 1); + EXPECT_FLOAT_RECT_EQ(expectedDamageRect, rootDamageRect); + + // CASE 5: Setting the update rect on child2, which is above child1, will + // not get blurred by child1, so it does not need to get expanded. + clearDamageForAllSurfaces(root.get()); + child2->setUpdateRect(FloatRect(0, 0, 1, 1)); + emulateDrawingOneFrame(root.get()); + + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + // Damage on child2 should be: position of updateRect offset by the child's position (11, 11), and not expanded by anything. + expectedDamageRect = FloatRect(11, 11, 1, 1); + EXPECT_FLOAT_RECT_EQ(expectedDamageRect, rootDamageRect); + + // CASE 6: Setting the update rect on child1 will also blur the damage, so + // that any pixels needed for the blur are redrawn in the current frame. + clearDamageForAllSurfaces(root.get()); + child1->setUpdateRect(FloatRect(0, 0, 1, 1)); + emulateDrawingOneFrame(root.get()); + + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + // Damage on child1 should be: position of updateRect offset by the child's position (100, 100), and expanded by the damage. + expectedDamageRect = FloatRect(100, 100, 1, 1); + expectedDamageRect.move(-outsetLeft, -outsetTop); + expectedDamageRect.expand(outsetLeft + outsetRight, outsetTop + outsetBottom); + EXPECT_FLOAT_RECT_EQ(expectedDamageRect, rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForAddingAndRemovingLayer) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + CCLayerImpl* child1 = root->children()[0].get(); + + // CASE 1: Adding a new layer should cause the appropriate damage. + // + clearDamageForAllSurfaces(root.get()); + { + OwnPtr<CCLayerImpl> child2 = CCLayerImpl::create(3); + child2->setPosition(FloatPoint(400, 380)); + child2->setAnchorPoint(FloatPoint::zero()); + child2->setBounds(IntSize(6, 8)); + child2->setContentBounds(IntSize(6, 8)); + child2->setDrawsContent(true); + root->addChild(child2.release()); + } + emulateDrawingOneFrame(root.get()); + + // Sanity check - all 3 layers should be on the same render surface; render surfaces are tested elsewhere. + ASSERT_EQ(3u, root->renderSurface()->layerList().size()); + + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(400, 380, 6, 8), rootDamageRect); + + // CASE 2: If the layer is removed, its entire old layer becomes exposed, not just the + // last update rect. + + // Advance one frame without damage so that we know the damage rect is not leftover from the previous case. + clearDamageForAllSurfaces(root.get()); + emulateDrawingOneFrame(root.get()); + EXPECT_TRUE(root->renderSurface()->damageTracker()->currentDamageRect().isEmpty()); + + // Then, test removing child1. + child1->removeFromParent(); + emulateDrawingOneFrame(root.get()); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(100, 100, 30, 30), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForNewUnchangedLayer) +{ + // If child2 is added to the layer tree, but it doesn't have any explicit damage of + // its own, it should still indeed damage the target surface. + + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + + clearDamageForAllSurfaces(root.get()); + { + OwnPtr<CCLayerImpl> child2 = CCLayerImpl::create(3); + child2->setPosition(FloatPoint(400, 380)); + child2->setAnchorPoint(FloatPoint::zero()); + child2->setBounds(IntSize(6, 8)); + child2->setContentBounds(IntSize(6, 8)); + child2->setDrawsContent(true); + child2->resetAllChangeTrackingForSubtree(); + // Sanity check the initial conditions of the test, if these asserts trigger, it + // means the test no longer actually covers the intended scenario. + ASSERT_FALSE(child2->layerPropertyChanged()); + ASSERT_TRUE(child2->updateRect().isEmpty()); + root->addChild(child2.release()); + } + emulateDrawingOneFrame(root.get()); + + // Sanity check - all 3 layers should be on the same render surface; render surfaces are tested elsewhere. + ASSERT_EQ(3u, root->renderSurface()->layerList().size()); + + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(400, 380, 6, 8), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForMultipleLayers) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + CCLayerImpl* child1 = root->children()[0].get(); + + // In this test we don't want the above tree manipulation to be considered part of the same frame. + clearDamageForAllSurfaces(root.get()); + { + OwnPtr<CCLayerImpl> child2 = CCLayerImpl::create(3); + child2->setPosition(FloatPoint(400, 380)); + child2->setAnchorPoint(FloatPoint::zero()); + child2->setBounds(IntSize(6, 8)); + child2->setContentBounds(IntSize(6, 8)); + child2->setDrawsContent(true); + root->addChild(child2.release()); + } + CCLayerImpl* child2 = root->children()[1].get(); + emulateDrawingOneFrame(root.get()); + + // Damaging two layers simultaneously should cause combined damage. + // - child1 update rect in surface space: FloatRect(100, 100, 1, 2); + // - child2 update rect in surface space: FloatRect(400, 380, 3, 4); + clearDamageForAllSurfaces(root.get()); + child1->setUpdateRect(FloatRect(0, 0, 1, 2)); + child2->setUpdateRect(FloatRect(0, 0, 3, 4)); + emulateDrawingOneFrame(root.get()); + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(100, 100, 303, 284), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForNestedSurfaces) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + CCLayerImpl* child1 = root->children()[0].get(); + CCLayerImpl* child2 = root->children()[1].get(); + CCLayerImpl* grandChild1 = root->children()[0]->children()[0].get(); + FloatRect childDamageRect; + FloatRect rootDamageRect; + + // CASE 1: Damage to a descendant surface should propagate properly to ancestor surface. + // + clearDamageForAllSurfaces(root.get()); + grandChild1->setOpacity(0.5); + emulateDrawingOneFrame(root.get()); + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(200, 200, 6, 8), childDamageRect); + EXPECT_FLOAT_RECT_EQ(FloatRect(300, 300, 6, 8), rootDamageRect); + + // CASE 2: Same as previous case, but with additional damage elsewhere that should be properly unioned. + // - child1 surface damage in root surface space: FloatRect(300, 300, 6, 8); + // - child2 damage in root surface space: FloatRect(11, 11, 18, 18); + clearDamageForAllSurfaces(root.get()); + grandChild1->setOpacity(0.7f); + child2->setOpacity(0.7f); + emulateDrawingOneFrame(root.get()); + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(200, 200, 6, 8), childDamageRect); + EXPECT_FLOAT_RECT_EQ(FloatRect(11, 11, 295, 297), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForSurfaceChangeFromDescendantLayer) +{ + // If descendant layer changes and affects the content bounds of the render surface, + // then the entire descendant surface should be damaged, and it should damage its + // ancestor surface with the old and new surface regions. + + // This is a tricky case, since only the first grandChild changes, but the entire + // surface should be marked dirty. + + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + CCLayerImpl* child1 = root->children()[0].get(); + CCLayerImpl* grandChild1 = root->children()[0]->children()[0].get(); + FloatRect childDamageRect; + FloatRect rootDamageRect; + + clearDamageForAllSurfaces(root.get()); + grandChild1->setPosition(FloatPoint(195, 205)); + emulateDrawingOneFrame(root.get()); + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + + // The new surface bounds should be damaged entirely, even though only one of the layers changed. + EXPECT_FLOAT_RECT_EQ(FloatRect(190, 190, 11, 23), childDamageRect); + + // Damage to the root surface should be the union of child1's *entire* render surface + // (in target space), and its old exposed area (also in target space). + EXPECT_FLOAT_RECT_EQ(FloatRect(290, 290, 16, 23), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForSurfaceChangeFromAncestorLayer) +{ + // An ancestor/owning layer changes that affects the position/transform of the render + // surface. Note that in this case, the layerPropertyChanged flag already propagates + // to the subtree (tested in CCLayerImpltest), which damages the entire child1 + // surface, but the damage tracker still needs the correct logic to compute the + // exposed region on the root surface. + + // FIXME: the expectations of this test case should change when we add support for a + // unique scissorRect per renderSurface. In that case, the child1 surface + // should be completely unchanged, since we are only transforming it, while the + // root surface would be damaged appropriately. + + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + CCLayerImpl* child1 = root->children()[0].get(); + FloatRect childDamageRect; + FloatRect rootDamageRect; + + clearDamageForAllSurfaces(root.get()); + child1->setPosition(FloatPoint(50, 50)); + emulateDrawingOneFrame(root.get()); + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + + // The new surface bounds should be damaged entirely. + EXPECT_FLOAT_RECT_EQ(FloatRect(190, 190, 16, 18), childDamageRect); + + // The entire child1 surface and the old exposed child1 surface should damage the root surface. + // - old child1 surface in target space: FloatRect(290, 290, 16, 18) + // - new child1 surface in target space: FloatRect(240, 240, 16, 18) + EXPECT_FLOAT_RECT_EQ(FloatRect(240, 240, 66, 68), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForAddingAndRemovingRenderSurfaces) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + CCLayerImpl* child1 = root->children()[0].get(); + FloatRect childDamageRect; + FloatRect rootDamageRect; + + // CASE 1: If a descendant surface disappears, its entire old area becomes exposed. + // + clearDamageForAllSurfaces(root.get()); + child1->setOpacity(1); + emulateDrawingOneFrame(root.get()); + + // Sanity check that there is only one surface now. + ASSERT_FALSE(child1->renderSurface()); + ASSERT_EQ(4u, root->renderSurface()->layerList().size()); + + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(290, 290, 16, 18), rootDamageRect); + + // CASE 2: If a descendant surface appears, its entire old area becomes exposed. + + // Cycle one frame of no change, just to sanity check that the next rect is not because of the old damage state. + clearDamageForAllSurfaces(root.get()); + emulateDrawingOneFrame(root.get()); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_TRUE(rootDamageRect.isEmpty()); + + // Then change the tree so that the render surface is added back. + clearDamageForAllSurfaces(root.get()); + child1->setOpacity(0.5); + emulateDrawingOneFrame(root.get()); + + // Sanity check that there is a new surface now. + ASSERT_TRUE(child1->renderSurface()); + EXPECT_EQ(3u, root->renderSurface()->layerList().size()); + EXPECT_EQ(2u, child1->renderSurface()->layerList().size()); + + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(190, 190, 16, 18), childDamageRect); + EXPECT_FLOAT_RECT_EQ(FloatRect(290, 290, 16, 18), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyNoDamageWhenNothingChanged) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + CCLayerImpl* child1 = root->children()[0].get(); + FloatRect childDamageRect; + FloatRect rootDamageRect; + + // CASE 1: If nothing changes, the damage rect should be empty. + // + clearDamageForAllSurfaces(root.get()); + emulateDrawingOneFrame(root.get()); + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_TRUE(childDamageRect.isEmpty()); + EXPECT_TRUE(rootDamageRect.isEmpty()); + + // CASE 2: If nothing changes twice in a row, the damage rect should still be empty. + // + clearDamageForAllSurfaces(root.get()); + emulateDrawingOneFrame(root.get()); + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_TRUE(childDamageRect.isEmpty()); + EXPECT_TRUE(rootDamageRect.isEmpty()); +} + +TEST_F(CCDamageTrackerTest, verifyNoDamageForUpdateRectThatDoesNotDrawContent) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + CCLayerImpl* child1 = root->children()[0].get(); + FloatRect childDamageRect; + FloatRect rootDamageRect; + + // In our specific tree, the update rect of child1 should not cause any damage to any + // surface because it does not actually draw content. + clearDamageForAllSurfaces(root.get()); + child1->setUpdateRect(FloatRect(0, 0, 1, 2)); + emulateDrawingOneFrame(root.get()); + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_TRUE(childDamageRect.isEmpty()); + EXPECT_TRUE(rootDamageRect.isEmpty()); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForReplica) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + CCLayerImpl* child1 = root->children()[0].get(); + CCLayerImpl* grandChild1 = child1->children()[0].get(); + CCLayerImpl* grandChild2 = child1->children()[1].get(); + + // Damage on a surface that has a reflection should cause the target surface to + // receive the surface's damage and the surface's reflected damage. + + // For this test case, we modify grandChild2, and add grandChild3 to extend the bounds + // of child1's surface. This way, we can test reflection changes without changing + // contentBounds of the surface. + grandChild2->setPosition(FloatPoint(180, 180)); + { + OwnPtr<CCLayerImpl> grandChild3 = CCLayerImpl::create(6); + grandChild3->setPosition(FloatPoint(240, 240)); + grandChild3->setAnchorPoint(FloatPoint::zero()); + grandChild3->setBounds(IntSize(10, 10)); + grandChild3->setContentBounds(IntSize(10, 10)); + grandChild3->setDrawsContent(true); + child1->addChild(grandChild3.release()); + } + child1->setOpacity(0.5); + emulateDrawingOneFrame(root.get()); + + // CASE 1: adding a reflection about the left edge of grandChild1. + // + clearDamageForAllSurfaces(root.get()); + { + OwnPtr<CCLayerImpl> grandChild1Replica = CCLayerImpl::create(7); + grandChild1Replica->setPosition(FloatPoint::zero()); + grandChild1Replica->setAnchorPoint(FloatPoint::zero()); + WebTransformationMatrix reflection; + reflection.scale3d(-1, 1, 1); + grandChild1Replica->setTransform(reflection); + grandChild1->setReplicaLayer(grandChild1Replica.release()); + } + emulateDrawingOneFrame(root.get()); + + FloatRect grandChildDamageRect = grandChild1->renderSurface()->damageTracker()->currentDamageRect(); + FloatRect childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + + // The grandChild surface damage should not include its own replica. The child + // surface damage should include the normal and replica surfaces. + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 6, 8), grandChildDamageRect); + EXPECT_FLOAT_RECT_EQ(FloatRect(194, 200, 12, 8), childDamageRect); + EXPECT_FLOAT_RECT_EQ(FloatRect(294, 300, 12, 8), rootDamageRect); + + // CASE 2: moving the descendant surface should cause both the original and reflected + // areas to be damaged on the target. + clearDamageForAllSurfaces(root.get()); + IntRect oldContentRect = child1->renderSurface()->contentRect(); + grandChild1->setPosition(FloatPoint(195, 205)); + emulateDrawingOneFrame(root.get()); + ASSERT_EQ(oldContentRect.width(), child1->renderSurface()->contentRect().width()); + ASSERT_EQ(oldContentRect.height(), child1->renderSurface()->contentRect().height()); + + grandChildDamageRect = grandChild1->renderSurface()->damageTracker()->currentDamageRect(); + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + + // The child surface damage should include normal and replica surfaces for both old and new locations. + // - old location in target space: FloatRect(194, 200, 12, 8) + // - new location in target space: FloatRect(189, 205, 12, 8) + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 6, 8), grandChildDamageRect); + EXPECT_FLOAT_RECT_EQ(FloatRect(189, 200, 17, 13), childDamageRect); + EXPECT_FLOAT_RECT_EQ(FloatRect(289, 300, 17, 13), rootDamageRect); + + // CASE 3: removing the reflection should cause the entire region including reflection + // to damage the target surface. + clearDamageForAllSurfaces(root.get()); + grandChild1->setReplicaLayer(nullptr); + emulateDrawingOneFrame(root.get()); + ASSERT_EQ(oldContentRect.width(), child1->renderSurface()->contentRect().width()); + ASSERT_EQ(oldContentRect.height(), child1->renderSurface()->contentRect().height()); + + EXPECT_FALSE(grandChild1->renderSurface()); + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + + EXPECT_FLOAT_RECT_EQ(FloatRect(189, 205, 12, 8), childDamageRect); + EXPECT_FLOAT_RECT_EQ(FloatRect(289, 305, 12, 8), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForMask) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + CCLayerImpl* child = root->children()[0].get(); + + // In the current implementation of the damage tracker, changes to mask layers should + // damage the entire corresponding surface. + + clearDamageForAllSurfaces(root.get()); + + // Set up the mask layer. + { + OwnPtr<CCLayerImpl> maskLayer = CCLayerImpl::create(3); + maskLayer->setPosition(child->position()); + maskLayer->setAnchorPoint(FloatPoint::zero()); + maskLayer->setBounds(child->bounds()); + maskLayer->setContentBounds(child->bounds()); + child->setMaskLayer(maskLayer.release()); + } + CCLayerImpl* maskLayer = child->maskLayer(); + + // Add opacity and a grandChild so that the render surface persists even after we remove the mask. + child->setOpacity(0.5); + { + OwnPtr<CCLayerImpl> grandChild = CCLayerImpl::create(4); + grandChild->setPosition(FloatPoint(2, 2)); + grandChild->setAnchorPoint(FloatPoint::zero()); + grandChild->setBounds(IntSize(2, 2)); + grandChild->setContentBounds(IntSize(2, 2)); + grandChild->setDrawsContent(true); + child->addChild(grandChild.release()); + } + emulateDrawingOneFrame(root.get()); + + // Sanity check that a new surface was created for the child. + ASSERT_TRUE(child->renderSurface()); + + // CASE 1: the updateRect on a mask layer should damage the entire target surface. + // + clearDamageForAllSurfaces(root.get()); + maskLayer->setUpdateRect(FloatRect(1, 2, 3, 4)); + emulateDrawingOneFrame(root.get()); + FloatRect childDamageRect = child->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 30, 30), childDamageRect); + + // CASE 2: a property change on the mask layer should damage the entire target surface. + // + + // Advance one frame without damage so that we know the damage rect is not leftover from the previous case. + clearDamageForAllSurfaces(root.get()); + emulateDrawingOneFrame(root.get()); + childDamageRect = child->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_TRUE(childDamageRect.isEmpty()); + + // Then test the property change. + clearDamageForAllSurfaces(root.get()); + maskLayer->setStackingOrderChanged(true); + + emulateDrawingOneFrame(root.get()); + childDamageRect = child->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 30, 30), childDamageRect); + + // CASE 3: removing the mask also damages the entire target surface. + // + + // Advance one frame without damage so that we know the damage rect is not leftover from the previous case. + clearDamageForAllSurfaces(root.get()); + emulateDrawingOneFrame(root.get()); + childDamageRect = child->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_TRUE(childDamageRect.isEmpty()); + + // Then test mask removal. + clearDamageForAllSurfaces(root.get()); + child->setMaskLayer(nullptr); + ASSERT_TRUE(child->layerPropertyChanged()); + emulateDrawingOneFrame(root.get()); + + // Sanity check that a render surface still exists. + ASSERT_TRUE(child->renderSurface()); + + childDamageRect = child->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 30, 30), childDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForReplicaMask) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + CCLayerImpl* child1 = root->children()[0].get(); + CCLayerImpl* grandChild1 = child1->children()[0].get(); + + // Changes to a replica's mask should not damage the original surface, because it is + // not masked. But it does damage the ancestor target surface. + + clearDamageForAllSurfaces(root.get()); + + // Create a reflection about the left edge of grandChild1. + { + OwnPtr<CCLayerImpl> grandChild1Replica = CCLayerImpl::create(6); + grandChild1Replica->setPosition(FloatPoint::zero()); + grandChild1Replica->setAnchorPoint(FloatPoint::zero()); + WebTransformationMatrix reflection; + reflection.scale3d(-1, 1, 1); + grandChild1Replica->setTransform(reflection); + grandChild1->setReplicaLayer(grandChild1Replica.release()); + } + CCLayerImpl* grandChild1Replica = grandChild1->replicaLayer(); + + // Set up the mask layer on the replica layer + { + OwnPtr<CCLayerImpl> replicaMaskLayer = CCLayerImpl::create(7); + replicaMaskLayer->setPosition(FloatPoint::zero()); + replicaMaskLayer->setAnchorPoint(FloatPoint::zero()); + replicaMaskLayer->setBounds(grandChild1->bounds()); + replicaMaskLayer->setContentBounds(grandChild1->bounds()); + grandChild1Replica->setMaskLayer(replicaMaskLayer.release()); + } + CCLayerImpl* replicaMaskLayer = grandChild1Replica->maskLayer(); + + emulateDrawingOneFrame(root.get()); + + // Sanity check that the appropriate render surfaces were created + ASSERT_TRUE(grandChild1->renderSurface()); + + // CASE 1: a property change on the mask should damage only the reflected region on the target surface. + clearDamageForAllSurfaces(root.get()); + replicaMaskLayer->setStackingOrderChanged(true); + emulateDrawingOneFrame(root.get()); + + FloatRect grandChildDamageRect = grandChild1->renderSurface()->damageTracker()->currentDamageRect(); + FloatRect childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + + EXPECT_TRUE(grandChildDamageRect.isEmpty()); + EXPECT_FLOAT_RECT_EQ(FloatRect(194, 200, 6, 8), childDamageRect); + + // CASE 2: removing the replica mask damages only the reflected region on the target surface. + // + clearDamageForAllSurfaces(root.get()); + grandChild1Replica->setMaskLayer(nullptr); + emulateDrawingOneFrame(root.get()); + + grandChildDamageRect = grandChild1->renderSurface()->damageTracker()->currentDamageRect(); + childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + + EXPECT_TRUE(grandChildDamageRect.isEmpty()); + EXPECT_FLOAT_RECT_EQ(FloatRect(194, 200, 6, 8), childDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForReplicaMaskWithAnchor) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithTwoSurfaces(); + CCLayerImpl* child1 = root->children()[0].get(); + CCLayerImpl* grandChild1 = child1->children()[0].get(); + + // Verify that the correct replicaOriginTransform is used for the replicaMask; + clearDamageForAllSurfaces(root.get()); + + grandChild1->setAnchorPoint(FloatPoint(1, 0)); // This is not exactly the anchor being tested, but by convention its expected to be the same as the replica's anchor point. + + { + OwnPtr<CCLayerImpl> grandChild1Replica = CCLayerImpl::create(6); + grandChild1Replica->setPosition(FloatPoint::zero()); + grandChild1Replica->setAnchorPoint(FloatPoint(1, 0)); // This is the anchor being tested. + WebTransformationMatrix reflection; + reflection.scale3d(-1, 1, 1); + grandChild1Replica->setTransform(reflection); + grandChild1->setReplicaLayer(grandChild1Replica.release()); + } + CCLayerImpl* grandChild1Replica = grandChild1->replicaLayer(); + + // Set up the mask layer on the replica layer + { + OwnPtr<CCLayerImpl> replicaMaskLayer = CCLayerImpl::create(7); + replicaMaskLayer->setPosition(FloatPoint::zero()); + replicaMaskLayer->setAnchorPoint(FloatPoint::zero()); // note, this is not the anchor being tested. + replicaMaskLayer->setBounds(grandChild1->bounds()); + replicaMaskLayer->setContentBounds(grandChild1->bounds()); + grandChild1Replica->setMaskLayer(replicaMaskLayer.release()); + } + CCLayerImpl* replicaMaskLayer = grandChild1Replica->maskLayer(); + + emulateDrawingOneFrame(root.get()); + + // Sanity check that the appropriate render surfaces were created + ASSERT_TRUE(grandChild1->renderSurface()); + + // A property change on the replicaMask should damage the reflected region on the target surface. + clearDamageForAllSurfaces(root.get()); + replicaMaskLayer->setStackingOrderChanged(true); + + emulateDrawingOneFrame(root.get()); + + FloatRect childDamageRect = child1->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(206, 200, 6, 8), childDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageWhenForcedFullDamage) +{ + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + CCLayerImpl* child = root->children()[0].get(); + + // Case 1: This test ensures that when the tracker is forced to have full damage, that + // it takes priority over any other partial damage. + // + clearDamageForAllSurfaces(root.get()); + child->setUpdateRect(FloatRect(10, 11, 12, 13)); + root->renderSurface()->damageTracker()->forceFullDamageNextUpdate(); + emulateDrawingOneFrame(root.get()); + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 500, 500), rootDamageRect); + + // Case 2: An additional sanity check that forcing full damage works even when nothing + // on the layer tree changed. + // + clearDamageForAllSurfaces(root.get()); + root->renderSurface()->damageTracker()->forceFullDamageNextUpdate(); + emulateDrawingOneFrame(root.get()); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(0, 0, 500, 500), rootDamageRect); +} + +TEST_F(CCDamageTrackerTest, verifyDamageForEmptyLayerList) +{ + // Though it should never happen, its a good idea to verify that the damage tracker + // does not crash when it receives an empty layerList. + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + root->createRenderSurface(); + + ASSERT_TRUE(root == root->renderTarget()); + CCRenderSurface* targetSurface = root->renderSurface(); + targetSurface->clearLayerList(); + targetSurface->damageTracker()->updateDamageTrackingState(targetSurface->layerList(), targetSurface->owningLayerId(), false, IntRect(), 0, WebFilterOperations()); + + FloatRect damageRect = targetSurface->damageTracker()->currentDamageRect(); + EXPECT_TRUE(damageRect.isEmpty()); +} + +TEST_F(CCDamageTrackerTest, verifyDamageAccumulatesUntilReset) +{ + // If damage is not cleared, it should accumulate. + + OwnPtr<CCLayerImpl> root = createAndSetUpTestTreeWithOneSurface(); + CCLayerImpl* child = root->children()[0].get(); + + clearDamageForAllSurfaces(root.get()); + child->setUpdateRect(FloatRect(10, 11, 1, 2)); + emulateDrawingOneFrame(root.get()); + + // Sanity check damage after the first frame; this isnt the actual test yet. + FloatRect rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(110, 111, 1, 2), rootDamageRect); + + // New damage, without having cleared the previous damage, should be unioned to the previous one. + child->setUpdateRect(FloatRect(20, 25, 1, 2)); + emulateDrawingOneFrame(root.get()); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_FLOAT_RECT_EQ(FloatRect(110, 111, 11, 16), rootDamageRect); + + // If we notify the damage tracker that we drew the damaged area, then damage should be emptied. + root->renderSurface()->damageTracker()->didDrawDamagedArea(); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_TRUE(rootDamageRect.isEmpty()); + + // Damage should remain empty even after one frame, since there's yet no new damage + emulateDrawingOneFrame(root.get()); + rootDamageRect = root->renderSurface()->damageTracker()->currentDamageRect(); + EXPECT_TRUE(rootDamageRect.isEmpty()); +} + +} // namespace diff --git a/cc/CCDebugBorderDrawQuad.cpp b/cc/CCDebugBorderDrawQuad.cpp new file mode 100644 index 0000000..d819824 --- /dev/null +++ b/cc/CCDebugBorderDrawQuad.cpp @@ -0,0 +1,32 @@ +// Copyright 2012 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 "CCDebugBorderDrawQuad.h" + +namespace WebCore { + +PassOwnPtr<CCDebugBorderDrawQuad> CCDebugBorderDrawQuad::create(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, SkColor color, int width) +{ + return adoptPtr(new CCDebugBorderDrawQuad(sharedQuadState, quadRect, color, width)); +} + +CCDebugBorderDrawQuad::CCDebugBorderDrawQuad(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, SkColor color, int width) + : CCDrawQuad(sharedQuadState, CCDrawQuad::DebugBorder, quadRect) + , m_color(color) + , m_width(width) +{ + m_quadOpaque = false; + if (SkColorGetA(m_color) < 255) + m_needsBlending = true; +} + +const CCDebugBorderDrawQuad* CCDebugBorderDrawQuad::materialCast(const CCDrawQuad* quad) +{ + ASSERT(quad->material() == CCDrawQuad::DebugBorder); + return static_cast<const CCDebugBorderDrawQuad*>(quad); +} + +} diff --git a/cc/CCDebugBorderDrawQuad.h b/cc/CCDebugBorderDrawQuad.h new file mode 100644 index 0000000..77f8877 --- /dev/null +++ b/cc/CCDebugBorderDrawQuad.h @@ -0,0 +1,35 @@ +// Copyright 2012 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. + +#ifndef CCDebugBorderDrawQuad_h +#define CCDebugBorderDrawQuad_h + +#include "CCDrawQuad.h" +#include "SkColor.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +#pragma pack(push, 4) + +class CCDebugBorderDrawQuad : public CCDrawQuad { +public: + static PassOwnPtr<CCDebugBorderDrawQuad> create(const CCSharedQuadState*, const IntRect&, SkColor, int width); + + SkColor color() const { return m_color; }; + int width() const { return m_width; } + + static const CCDebugBorderDrawQuad* materialCast(const CCDrawQuad*); +private: + CCDebugBorderDrawQuad(const CCSharedQuadState*, const IntRect&, SkColor, int width); + + SkColor m_color; + int m_width; +}; + +#pragma pack(pop) + +} + +#endif diff --git a/cc/CCDebugRectHistory.cpp b/cc/CCDebugRectHistory.cpp new file mode 100644 index 0000000..6cd7c99 --- /dev/null +++ b/cc/CCDebugRectHistory.cpp @@ -0,0 +1,116 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) +#include "CCDebugRectHistory.h" + +#include "CCDamageTracker.h" +#include "CCLayerImpl.h" +#include "CCLayerTreeHost.h" +#include "CCMathUtil.h" + +namespace WebCore { + +CCDebugRectHistory::CCDebugRectHistory() +{ +} + +void CCDebugRectHistory::saveDebugRectsForCurrentFrame(CCLayerImpl* rootLayer, const Vector<CCLayerImpl*>& renderSurfaceLayerList, const Vector<IntRect>& occludingScreenSpaceRects, const CCLayerTreeSettings& settings) +{ + // For now, clear all rects from previous frames. In the future we may want to store + // all debug rects for a history of many frames. + m_debugRects.clear(); + + if (settings.showPaintRects) + savePaintRects(rootLayer); + + if (settings.showPropertyChangedRects) + savePropertyChangedRects(renderSurfaceLayerList); + + if (settings.showSurfaceDamageRects) + saveSurfaceDamageRects(renderSurfaceLayerList); + + if (settings.showScreenSpaceRects) + saveScreenSpaceRects(renderSurfaceLayerList); + + if (settings.showOccludingRects) + saveOccludingRects(occludingScreenSpaceRects); +} + + +void CCDebugRectHistory::savePaintRects(CCLayerImpl* layer) +{ + // We would like to visualize where any layer's paint rect (update rect) has changed, + // regardless of whether this layer is skipped for actual drawing or not. Therefore + // we traverse recursively over all layers, not just the render surface list. + + if (!layer->updateRect().isEmpty() && layer->drawsContent()) { + FloatRect updateContentRect = layer->updateRect(); + updateContentRect.scale(layer->contentBounds().width() / static_cast<float>(layer->bounds().width()), layer->contentBounds().height() / static_cast<float>(layer->bounds().height())); + m_debugRects.append(CCDebugRect(PaintRectType, CCMathUtil::mapClippedRect(layer->screenSpaceTransform(), updateContentRect))); + } + + for (unsigned i = 0; i < layer->children().size(); ++i) + savePaintRects(layer->children()[i].get()); +} + +void CCDebugRectHistory::savePropertyChangedRects(const Vector<CCLayerImpl*>& renderSurfaceLayerList) +{ + for (int surfaceIndex = renderSurfaceLayerList.size() - 1; surfaceIndex >= 0 ; --surfaceIndex) { + CCLayerImpl* renderSurfaceLayer = renderSurfaceLayerList[surfaceIndex]; + CCRenderSurface* renderSurface = renderSurfaceLayer->renderSurface(); + ASSERT(renderSurface); + + const Vector<CCLayerImpl*>& layerList = renderSurface->layerList(); + for (unsigned layerIndex = 0; layerIndex < layerList.size(); ++layerIndex) { + CCLayerImpl* layer = layerList[layerIndex]; + + if (CCLayerTreeHostCommon::renderSurfaceContributesToTarget<CCLayerImpl>(layer, renderSurfaceLayer->id())) + continue; + + if (layer->layerIsAlwaysDamaged()) + continue; + + if (layer->layerPropertyChanged() || layer->layerSurfacePropertyChanged()) + m_debugRects.append(CCDebugRect(PropertyChangedRectType, CCMathUtil::mapClippedRect(layer->screenSpaceTransform(), FloatRect(FloatPoint::zero(), layer->contentBounds())))); + } + } +} + +void CCDebugRectHistory::saveSurfaceDamageRects(const Vector<CCLayerImpl* >& renderSurfaceLayerList) +{ + for (int surfaceIndex = renderSurfaceLayerList.size() - 1; surfaceIndex >= 0 ; --surfaceIndex) { + CCLayerImpl* renderSurfaceLayer = renderSurfaceLayerList[surfaceIndex]; + CCRenderSurface* renderSurface = renderSurfaceLayer->renderSurface(); + ASSERT(renderSurface); + + m_debugRects.append(CCDebugRect(SurfaceDamageRectType, CCMathUtil::mapClippedRect(renderSurface->screenSpaceTransform(), renderSurface->damageTracker()->currentDamageRect()))); + } +} + +void CCDebugRectHistory::saveScreenSpaceRects(const Vector<CCLayerImpl* >& renderSurfaceLayerList) +{ + for (int surfaceIndex = renderSurfaceLayerList.size() - 1; surfaceIndex >= 0 ; --surfaceIndex) { + CCLayerImpl* renderSurfaceLayer = renderSurfaceLayerList[surfaceIndex]; + CCRenderSurface* renderSurface = renderSurfaceLayer->renderSurface(); + ASSERT(renderSurface); + + m_debugRects.append(CCDebugRect(ScreenSpaceRectType, CCMathUtil::mapClippedRect(renderSurface->screenSpaceTransform(), renderSurface->contentRect()))); + + if (renderSurfaceLayer->replicaLayer()) + m_debugRects.append(CCDebugRect(ReplicaScreenSpaceRectType, CCMathUtil::mapClippedRect(renderSurface->replicaScreenSpaceTransform(), renderSurface->contentRect()))); + } +} + +void CCDebugRectHistory::saveOccludingRects(const Vector<IntRect>& occludingRects) +{ + for (size_t i = 0; i < occludingRects.size(); ++i) + m_debugRects.append(CCDebugRect(OccludingRectType, occludingRects[i])); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCDebugRectHistory.h b/cc/CCDebugRectHistory.h new file mode 100644 index 0000000..af542ad --- /dev/null +++ b/cc/CCDebugRectHistory.h @@ -0,0 +1,83 @@ +// Copyright 2012 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. + +#ifndef CCDebugRectHistory_h +#define CCDebugRectHistory_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "FloatRect.h" +#include "IntRect.h" +#include <wtf/Noncopyable.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class CCLayerImpl; +struct CCLayerTreeSettings; + +// There are currently six types of debug rects: +// +// - Paint rects (update rects): regions of a layer that needed to be re-uploaded to the +// texture resource; in most cases implying that WebCore had to repaint them, too. +// +// - Property-changed rects: enclosing bounds of layers that cause changes to the screen +// even if the layer did not change internally. (For example, if the layer's opacity or +// position changes.) +// +// - Surface damage rects: the aggregate damage on a target surface that is caused by all +// layers and surfaces that contribute to it. This includes (1) paint rects, (2) property- +// changed rects, and (3) newly exposed areas. +// +// - Screen space rects: this is the region the contents occupy in screen space. +// +// - Replica screen space rects: this is the region the replica's contents occupy in screen space. +// +// - Occluding rects: these are the regions that contribute to the occluded region. +// +enum DebugRectType { PaintRectType, PropertyChangedRectType, SurfaceDamageRectType, ScreenSpaceRectType, ReplicaScreenSpaceRectType, OccludingRectType }; + +struct CCDebugRect { + CCDebugRect(DebugRectType newType, FloatRect newRect) + : type(newType) + , rect(newRect) { } + + DebugRectType type; + FloatRect rect; +}; + +// This class maintains a history of rects of various types that can be used +// for debugging purposes. The overhead of collecting rects is performed only if +// the appropriate CCLayerTreeSettings are enabled. +class CCDebugRectHistory { + WTF_MAKE_NONCOPYABLE(CCDebugRectHistory); +public: + static PassOwnPtr<CCDebugRectHistory> create() + { + return adoptPtr(new CCDebugRectHistory()); + } + + // Note: Saving debug rects must happen before layers' change tracking is reset. + void saveDebugRectsForCurrentFrame(CCLayerImpl* rootLayer, const Vector<CCLayerImpl*>& renderSurfaceLayerList, const Vector<IntRect>& occludingScreenSpaceRects, const CCLayerTreeSettings&); + + const Vector<CCDebugRect>& debugRects() { return m_debugRects; } + +private: + CCDebugRectHistory(); + + void savePaintRects(CCLayerImpl*); + void savePropertyChangedRects(const Vector<CCLayerImpl*>& renderSurfaceLayerList); + void saveSurfaceDamageRects(const Vector<CCLayerImpl* >& renderSurfaceLayerList); + void saveScreenSpaceRects(const Vector<CCLayerImpl* >& renderSurfaceLayerList); + void saveOccludingRects(const Vector<IntRect>& occludingScreenSpaceRects); + + Vector<CCDebugRect> m_debugRects; +}; + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/CCDelayBasedTimeSource.cpp b/cc/CCDelayBasedTimeSource.cpp new file mode 100644 index 0000000..344e52c --- /dev/null +++ b/cc/CCDelayBasedTimeSource.cpp @@ -0,0 +1,220 @@ +// 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 "config.h" + +#include "CCDelayBasedTimeSource.h" + +#include "TraceEvent.h" +#include <algorithm> +#include <wtf/CurrentTime.h> +#include <wtf/MathExtras.h> + +namespace WebCore { + +namespace { + +// doubleTickThreshold prevents ticks from running within the specified fraction of an interval. +// This helps account for jitter in the timebase as well as quick timer reactivation. +const double doubleTickThreshold = 0.25; + +// intervalChangeThreshold is the fraction of the interval that will trigger an immediate interval change. +// phaseChangeThreshold is the fraction of the interval that will trigger an immediate phase change. +// If the changes are within the thresholds, the change will take place on the next tick. +// If either change is outside the thresholds, the next tick will be canceled and reissued immediately. +const double intervalChangeThreshold = 0.25; +const double phaseChangeThreshold = 0.25; + +} + + +PassRefPtr<CCDelayBasedTimeSource> CCDelayBasedTimeSource::create(double interval, CCThread* thread) +{ + return adoptRef(new CCDelayBasedTimeSource(interval, thread)); +} + +CCDelayBasedTimeSource::CCDelayBasedTimeSource(double intervalSeconds, CCThread* thread) + : m_client(0) + , m_hasTickTarget(false) + , m_lastTickTime(0) + , m_currentParameters(intervalSeconds, 0) + , m_nextParameters(intervalSeconds, 0) + , m_state(STATE_INACTIVE) + , m_timer(thread, this) +{ +} + +void CCDelayBasedTimeSource::setActive(bool active) +{ + TRACE_EVENT1("cc", "CCDelayBasedTimeSource::setActive", "active", active); + if (!active) { + m_state = STATE_INACTIVE; + m_timer.stop(); + return; + } + + if (m_state == STATE_STARTING || m_state == STATE_ACTIVE) + return; + + if (!m_hasTickTarget) { + // Becoming active the first time is deferred: we post a 0-delay task. When + // it runs, we use that to establish the timebase, become truly active, and + // fire the first tick. + m_state = STATE_STARTING; + m_timer.startOneShot(0); + return; + } + + m_state = STATE_ACTIVE; + + double now = monotonicTimeNow(); + postNextTickTask(now); +} + +double CCDelayBasedTimeSource::lastTickTime() +{ + return m_lastTickTime; +} + +double CCDelayBasedTimeSource::nextTickTimeIfActivated() +{ + return active() ? m_currentParameters.tickTarget : nextTickTarget(monotonicTimeNow()); +} + +void CCDelayBasedTimeSource::onTimerFired() +{ + ASSERT(m_state != STATE_INACTIVE); + + double now = monotonicTimeNow(); + m_lastTickTime = now; + + if (m_state == STATE_STARTING) { + setTimebaseAndInterval(now, m_currentParameters.interval); + m_state = STATE_ACTIVE; + } + + postNextTickTask(now); + + // Fire the tick + if (m_client) + m_client->onTimerTick(); +} + +void CCDelayBasedTimeSource::setTimebaseAndInterval(double timebase, double intervalSeconds) +{ + m_nextParameters.interval = intervalSeconds; + m_nextParameters.tickTarget = timebase; + m_hasTickTarget = true; + + if (m_state != STATE_ACTIVE) { + // If we aren't active, there's no need to reset the timer. + return; + } + + // If the change in interval is larger than the change threshold, + // request an immediate reset. + double intervalDelta = std::abs(intervalSeconds - m_currentParameters.interval); + double intervalChange = intervalDelta / intervalSeconds; + if (intervalChange > intervalChangeThreshold) { + setActive(false); + setActive(true); + return; + } + + // If the change in phase is greater than the change threshold in either + // direction, request an immediate reset. This logic might result in a false + // negative if there is a simultaneous small change in the interval and the + // fmod just happens to return something near zero. Assuming the timebase + // is very recent though, which it should be, we'll still be ok because the + // old clock and new clock just happen to line up. + double targetDelta = std::abs(timebase - m_currentParameters.tickTarget); + double phaseChange = fmod(targetDelta, intervalSeconds) / intervalSeconds; + if (phaseChange > phaseChangeThreshold && phaseChange < (1.0 - phaseChangeThreshold)) { + setActive(false); + setActive(true); + return; + } +} + +double CCDelayBasedTimeSource::monotonicTimeNow() const +{ + return monotonicallyIncreasingTime(); +} + +// This code tries to achieve an average tick rate as close to m_intervalMs as possible. +// To do this, it has to deal with a few basic issues: +// 1. postDelayedTask can delay only at a millisecond granularity. So, 16.666 has to +// posted as 16 or 17. +// 2. A delayed task may come back a bit late (a few ms), or really late (frames later) +// +// The basic idea with this scheduler here is to keep track of where we *want* to run in +// m_tickTarget. We update this with the exact interval. +// +// Then, when we post our task, we take the floor of (m_tickTarget and now()). If we +// started at now=0, and 60FPs: +// now=0 target=16.667 postDelayedTask(16) +// +// When our callback runs, we figure out how far off we were from that goal. Because of the flooring +// operation, and assuming our timer runs exactly when it should, this yields: +// now=16 target=16.667 +// +// Since we can't post a 0.667 ms task to get to now=16, we just treat this as a tick. Then, +// we update target to be 33.333. We now post another task based on the difference between our target +// and now: +// now=16 tickTarget=16.667 newTarget=33.333 --> postDelayedTask(floor(33.333 - 16)) --> postDelayedTask(17) +// +// Over time, with no late tasks, this leads to us posting tasks like this: +// now=0 tickTarget=0 newTarget=16.667 --> tick(), postDelayedTask(16) +// now=16 tickTarget=16.667 newTarget=33.333 --> tick(), postDelayedTask(17) +// now=33 tickTarget=33.333 newTarget=50.000 --> tick(), postDelayedTask(17) +// now=50 tickTarget=50.000 newTarget=66.667 --> tick(), postDelayedTask(16) +// +// We treat delays in tasks differently depending on the amount of delay we encounter. Suppose we +// posted a task with a target=16.667: +// Case 1: late but not unrecoverably-so +// now=18 tickTarget=16.667 +// +// Case 2: so late we obviously missed the tick +// now=25.0 tickTarget=16.667 +// +// We treat the first case as a tick anyway, and assume the delay was +// unusual. Thus, we compute the newTarget based on the old timebase: +// now=18 tickTarget=16.667 newTarget=33.333 --> tick(), postDelayedTask(floor(33.333-18)) --> postDelayedTask(15) +// This brings us back to 18+15 = 33, which was where we would have been if the task hadn't been late. +// +// For the really late delay, we we move to the next logical tick. The timebase is not reset. +// now=37 tickTarget=16.667 newTarget=50.000 --> tick(), postDelayedTask(floor(50.000-37)) --> postDelayedTask(13) +// +// Note, that in the above discussion, times are expressed in milliseconds, but in the code, seconds are used. +double CCDelayBasedTimeSource::nextTickTarget(double now) +{ + double newInterval = m_nextParameters.interval; + double intervalsElapsed = floor((now - m_nextParameters.tickTarget) / newInterval); + double lastEffectiveTick = m_nextParameters.tickTarget + newInterval * intervalsElapsed; + double newTickTarget = lastEffectiveTick + newInterval; + ASSERT(newTickTarget > now); + + // Avoid double ticks when: + // 1) Turning off the timer and turning it right back on. + // 2) Jittery data is passed to setTimebaseAndInterval(). + if (newTickTarget - m_lastTickTime <= newInterval * doubleTickThreshold) + newTickTarget += newInterval; + + return newTickTarget; +} + +void CCDelayBasedTimeSource::postNextTickTask(double now) +{ + double newTickTarget = nextTickTarget(now); + + // Post another task *before* the tick and update state + double delay = newTickTarget - now; + ASSERT(delay <= m_nextParameters.interval * (1.0 + doubleTickThreshold)); + m_timer.startOneShot(delay); + + m_nextParameters.tickTarget = newTickTarget; + m_currentParameters = m_nextParameters; +} + +} diff --git a/cc/CCDelayBasedTimeSource.h b/cc/CCDelayBasedTimeSource.h new file mode 100644 index 0000000..9565983 --- /dev/null +++ b/cc/CCDelayBasedTimeSource.h @@ -0,0 +1,78 @@ +// 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. + +#ifndef CCDelayBasedTimeSource_h +#define CCDelayBasedTimeSource_h + +#include "CCTimeSource.h" +#include "CCTimer.h" +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class CCThread; + +// This timer implements a time source that achieves the specified interval +// in face of millisecond-precision delayed callbacks and random queueing delays. +class CCDelayBasedTimeSource : public CCTimeSource, CCTimerClient { +public: + static PassRefPtr<CCDelayBasedTimeSource> create(double intervalSeconds, CCThread*); + + virtual ~CCDelayBasedTimeSource() { } + + virtual void setClient(CCTimeSourceClient* client) OVERRIDE { m_client = client; } + + // CCTimeSource implementation + virtual void setTimebaseAndInterval(double timebase, double intervalSeconds) OVERRIDE; + + virtual void setActive(bool) OVERRIDE; + virtual bool active() const OVERRIDE { return m_state != STATE_INACTIVE; } + + // Get the last and next tick times. + virtual double lastTickTime() OVERRIDE; + virtual double nextTickTimeIfActivated() OVERRIDE; + + // CCTimerClient implementation. + virtual void onTimerFired() OVERRIDE; + + // Virtual for testing. + virtual double monotonicTimeNow() const; + +protected: + CCDelayBasedTimeSource(double interval, CCThread*); + double nextTickTarget(double now); + void postNextTickTask(double now); + + enum State { + STATE_INACTIVE, + STATE_STARTING, + STATE_ACTIVE, + }; + + struct Parameters { + Parameters(double interval, double tickTarget) + : interval(interval), tickTarget(tickTarget) + { } + double interval; + double tickTarget; + }; + + CCTimeSourceClient* m_client; + bool m_hasTickTarget; + double m_lastTickTime; + + // m_currentParameters should only be written by postNextTickTask. + // m_nextParameters will take effect on the next call to postNextTickTask. + // Maintaining a pending set of parameters allows nextTickTime() to always + // reflect the actual time we expect onTimerFired to be called. + Parameters m_currentParameters; + Parameters m_nextParameters; + + State m_state; + CCThread* m_thread; + CCTimer m_timer; +}; + +} +#endif // CCDelayBasedTimeSource_h diff --git a/cc/CCDelayBasedTimeSourceTest.cpp b/cc/CCDelayBasedTimeSourceTest.cpp new file mode 100644 index 0000000..5e9f49b --- /dev/null +++ b/cc/CCDelayBasedTimeSourceTest.cpp @@ -0,0 +1,385 @@ +// 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 "config.h" + +#include "CCDelayBasedTimeSource.h" + +#include "CCSchedulerTestCommon.h" +#include "CCThread.h" +#include <gtest/gtest.h> +#include <wtf/RefPtr.h> + +using namespace WTF; +using namespace WebCore; +using namespace WebKitTests; + +namespace { + +TEST(CCDelayBasedTimeSourceTest, TaskPostedAndTickCalled) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(1.0 / 60.0, &thread); + timer->setClient(&client); + + timer->setMonotonicTimeNow(0); + timer->setActive(true); + EXPECT_TRUE(timer->active()); + EXPECT_TRUE(thread.hasPendingTask()); + + timer->setMonotonicTimeNow(0.016); + thread.runPendingTask(); + EXPECT_TRUE(timer->active()); + EXPECT_TRUE(client.tickCalled()); +} + +TEST(CCDelayBasedTimeSource, TickNotCalledWithTaskPosted) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(1.0 / 60.0, &thread); + timer->setClient(&client); + timer->setActive(true); + EXPECT_TRUE(thread.hasPendingTask()); + timer->setActive(false); + thread.runPendingTask(); + EXPECT_FALSE(client.tickCalled()); +} + +TEST(CCDelayBasedTimeSource, StartTwiceEnqueuesOneTask) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(1.0 / 60.0, &thread); + timer->setClient(&client); + timer->setActive(true); + EXPECT_TRUE(thread.hasPendingTask()); + thread.reset(); + timer->setActive(true); + EXPECT_FALSE(thread.hasPendingTask()); +} + +TEST(CCDelayBasedTimeSource, StartWhenRunningDoesntTick) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(1.0 / 60.0, &thread); + timer->setClient(&client); + timer->setActive(true); + thread.runPendingTask(); + thread.reset(); + timer->setActive(true); + EXPECT_FALSE(thread.hasPendingTask()); +} + +// At 60Hz, when the tick returns at exactly the requested next time, make sure +// a 16ms next delay is posted. +TEST(CCDelayBasedTimeSource, NextDelaySaneWhenExactlyOnRequestedTime) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + double interval = 1.0 / 60.0; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval, &thread); + timer->setClient(&client); + timer->setActive(true); + // Run the first task, as that activates the timer and picks up a timebase. + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + timer->setMonotonicTimeNow(interval); + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); +} + +// At 60Hz, when the tick returns at slightly after the requested next time, make sure +// a 16ms next delay is posted. +TEST(CCDelayBasedTimeSource, NextDelaySaneWhenSlightlyAfterRequestedTime) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + double interval = 1.0 / 60.0; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval, &thread); + timer->setClient(&client); + timer->setActive(true); + // Run the first task, as that activates the timer and picks up a timebase. + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + timer->setMonotonicTimeNow(interval + 0.0000001); + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); +} + +// At 60Hz, when the tick returns at exactly 2*interval after the requested next time, make sure +// a 16ms next delay is posted. +TEST(CCDelayBasedTimeSource, NextDelaySaneWhenExactlyTwiceAfterRequestedTime) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + double interval = 1.0 / 60.0; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval, &thread); + timer->setClient(&client); + timer->setActive(true); + // Run the first task, as that activates the timer and picks up a timebase. + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + timer->setMonotonicTimeNow(2*interval); + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); +} + +// At 60Hz, when the tick returns at 2*interval and a bit after the requested next time, make sure +// a 16ms next delay is posted. +TEST(CCDelayBasedTimeSource, NextDelaySaneWhenSlightlyAfterTwiceRequestedTime) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + double interval = 1.0 / 60.0; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval, &thread); + timer->setClient(&client); + timer->setActive(true); + // Run the first task, as that activates the timer and picks up a timebase. + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + timer->setMonotonicTimeNow(2*interval + 0.0000001); + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); +} + +// At 60Hz, when the tick returns halfway to the next frame time, make sure +// a correct next delay value is posted. +TEST(CCDelayBasedTimeSource, NextDelaySaneWhenHalfAfterRequestedTime) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + double interval = 1.0 / 60.0; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval, &thread); + timer->setClient(&client); + timer->setActive(true); + // Run the first task, as that activates the timer and picks up a timebase. + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + timer->setMonotonicTimeNow(interval + interval * 0.5); + thread.runPendingTask(); + + EXPECT_EQ(8, thread.pendingDelayMs()); +} + +// If the timebase and interval are updated with a jittery source, we want to +// make sure we do not double tick. +TEST(CCDelayBasedTimeSource, SaneHandlingOfJitteryTimebase) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + double interval = 1.0 / 60.0; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval, &thread); + timer->setClient(&client); + timer->setActive(true); + // Run the first task, as that activates the timer and picks up a timebase. + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + // Jitter timebase ~1ms late + timer->setTimebaseAndInterval(interval + 0.001, interval); + timer->setMonotonicTimeNow(interval); + thread.runPendingTask(); + + // Without double tick prevention, pendingDelayMs would be 1. + EXPECT_EQ(17, thread.pendingDelayMs()); + + // Jitter timebase ~1ms early + timer->setTimebaseAndInterval(interval * 2 - 0.001, interval); + timer->setMonotonicTimeNow(interval * 2); + thread.runPendingTask(); + + EXPECT_EQ(15, thread.pendingDelayMs()); +} + +TEST(CCDelayBasedTimeSource, HanldlesSignificantTimebaseChangesImmediately) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + double interval = 1.0 / 60.0; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval, &thread); + timer->setClient(&client); + timer->setActive(true); + // Run the first task, as that activates the timer and picks up a timebase. + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + // Tick, then shift timebase by +7ms. + timer->setMonotonicTimeNow(interval); + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + client.reset(); + thread.runPendingTaskOnOverwrite(true); + timer->setTimebaseAndInterval(interval + 0.0070001, interval); + thread.runPendingTaskOnOverwrite(false); + + EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled. + EXPECT_EQ(7, thread.pendingDelayMs()); + + // Tick, then shift timebase by -7ms. + timer->setMonotonicTimeNow(interval + 0.0070001); + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + client.reset(); + thread.runPendingTaskOnOverwrite(true); + timer->setTimebaseAndInterval(interval, interval); + thread.runPendingTaskOnOverwrite(false); + + EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled. + EXPECT_EQ(16-7, thread.pendingDelayMs()); +} + +TEST(CCDelayBasedTimeSource, HanldlesSignificantIntervalChangesImmediately) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + double interval = 1.0 / 60.0; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval, &thread); + timer->setClient(&client); + timer->setActive(true); + // Run the first task, as that activates the timer and picks up a timebase. + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + // Tick, then double the interval. + timer->setMonotonicTimeNow(interval); + thread.runPendingTask(); + + EXPECT_EQ(16, thread.pendingDelayMs()); + + client.reset(); + thread.runPendingTaskOnOverwrite(true); + timer->setTimebaseAndInterval(interval, interval * 2); + thread.runPendingTaskOnOverwrite(false); + + EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled. + EXPECT_EQ(33, thread.pendingDelayMs()); + + // Tick, then halve the interval. + timer->setMonotonicTimeNow(interval * 3); + thread.runPendingTask(); + + EXPECT_EQ(33, thread.pendingDelayMs()); + + client.reset(); + thread.runPendingTaskOnOverwrite(true); + timer->setTimebaseAndInterval(interval * 3, interval); + thread.runPendingTaskOnOverwrite(false); + + EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled. + EXPECT_EQ(16, thread.pendingDelayMs()); +} + +TEST(CCDelayBasedTimeSourceTest, AchievesTargetRateWithNoNoise) +{ + int numIterations = 1000; + + FakeCCThread thread; + FakeCCTimeSourceClient client; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(1.0 / 60.0, &thread); + timer->setClient(&client); + timer->setActive(true); + + double totalFrameTime = 0; + for (int i = 0; i < numIterations; ++i) { + long long delayMs = thread.pendingDelayMs(); + + // accumulate the "delay" + totalFrameTime += delayMs / 1000.0; + + // Run the callback exactly when asked + double now = timer->monotonicTimeNow() + delayMs / 1000.0; + timer->setMonotonicTimeNow(now); + thread.runPendingTask(); + } + double averageInterval = totalFrameTime / static_cast<double>(numIterations); + EXPECT_NEAR(1.0 / 60.0, averageInterval, 0.1); +} + +TEST(CCDelayBasedTimeSource, TestDeactivateWhilePending) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(1.0 / 60.0, &thread); + timer->setClient(&client); + timer->setActive(true); // Should post a task. + timer->setActive(false); + timer.clear(); + thread.runPendingTask(); // Should run the posted task without crashing. +} + +TEST(CCDelayBasedTimeSource, TestDeactivateAndReactivateBeforeNextTickTime) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(1.0 / 60.0, &thread); + timer->setClient(&client); + + // Should run the activate task, and pick up a new timebase. + timer->setActive(true); + timer->setMonotonicTimeNow(0); + thread.runPendingTask(); + + // Stop the timer + timer->setActive(false); + + // Task will be pending anyway, run it + thread.runPendingTask(); + + // Start the timer again, but before the next tick time the timer previously + // planned on using. That same tick time should still be targeted. + timer->setMonotonicTimeNow(0.004); + timer->setActive(true); + EXPECT_EQ(12, thread.pendingDelayMs()); +} + +TEST(CCDelayBasedTimeSource, TestDeactivateAndReactivateAfterNextTickTime) +{ + FakeCCThread thread; + FakeCCTimeSourceClient client; + RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(1.0 / 60.0, &thread); + timer->setClient(&client); + + // Should run the activate task, and pick up a new timebase. + timer->setActive(true); + timer->setMonotonicTimeNow(0); + thread.runPendingTask(); + + // Stop the timer + timer->setActive(false); + + // Task will be pending anyway, run it + thread.runPendingTask(); + + // Start the timer again, but before the next tick time the timer previously + // planned on using. That same tick time should still be targeted. + timer->setMonotonicTimeNow(0.02); + timer->setActive(true); + EXPECT_EQ(13, thread.pendingDelayMs()); +} + +} diff --git a/cc/CCDirectRenderer.cpp b/cc/CCDirectRenderer.cpp new file mode 100644 index 0000000..f4b9731 --- /dev/null +++ b/cc/CCDirectRenderer.cpp @@ -0,0 +1,212 @@ +// Copyright 2012 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 "CCDirectRenderer.h" + +#include "CCMathUtil.h" +#include <public/WebTransformationMatrix.h> + +using WebKit::WebTransformationMatrix; + +static WebTransformationMatrix orthoProjectionMatrix(float left, float right, float bottom, float top) +{ + // Use the standard formula to map the clipping frustum to the cube from + // [-1, -1, -1] to [1, 1, 1]. + float deltaX = right - left; + float deltaY = top - bottom; + WebTransformationMatrix proj; + if (!deltaX || !deltaY) + return proj; + proj.setM11(2.0f / deltaX); + proj.setM41(-(right + left) / deltaX); + proj.setM22(2.0f / deltaY); + proj.setM42(-(top + bottom) / deltaY); + + // Z component of vertices is always set to zero as we don't use the depth buffer + // while drawing. + proj.setM33(0); + + return proj; +} + +static WebTransformationMatrix windowMatrix(int x, int y, int width, int height) +{ + WebTransformationMatrix canvas; + + // Map to window position and scale up to pixel coordinates. + canvas.translate3d(x, y, 0); + canvas.scale3d(width, height, 0); + + // Map from ([-1, -1] to [1, 1]) -> ([0, 0] to [1, 1]) + canvas.translate3d(0.5, 0.5, 0.5); + canvas.scale3d(0.5, 0.5, 0.5); + + return canvas; +} + +namespace WebCore { +// +// static +FloatRect CCDirectRenderer::quadVertexRect() +{ + return FloatRect(-0.5, -0.5, 1, 1); +} + +// static +void CCDirectRenderer::quadRectTransform(WebKit::WebTransformationMatrix* quadRectTransform, const WebKit::WebTransformationMatrix& quadTransform, const FloatRect& quadRect) +{ + *quadRectTransform = quadTransform; + quadRectTransform->translate(0.5 * quadRect.width() + quadRect.x(), 0.5 * quadRect.height() + quadRect.y()); + quadRectTransform->scaleNonUniform(quadRect.width(), quadRect.height()); +} + +// static +void CCDirectRenderer::initializeMatrices(DrawingFrame& frame, const IntRect& drawRect, bool flipY) +{ + if (flipY) + frame.projectionMatrix = orthoProjectionMatrix(drawRect.x(), drawRect.maxX(), drawRect.maxY(), drawRect.y()); + else + frame.projectionMatrix = orthoProjectionMatrix(drawRect.x(), drawRect.maxX(), drawRect.y(), drawRect.maxY()); + frame.windowMatrix = windowMatrix(0, 0, drawRect.width(), drawRect.height()); + frame.flippedY = flipY; +} + +// static +IntRect CCDirectRenderer::moveScissorToWindowSpace(const DrawingFrame& frame, FloatRect scissorRect) +{ + IntRect scissorRectInCanvasSpace = enclosingIntRect(scissorRect); + // The scissor coordinates must be supplied in viewport space so we need to offset + // by the relative position of the top left corner of the current render pass. + IntRect framebufferOutputRect = frame.currentRenderPass->outputRect(); + scissorRectInCanvasSpace.setX(scissorRectInCanvasSpace.x() - framebufferOutputRect.x()); + if (frame.flippedY && !frame.currentTexture) + scissorRectInCanvasSpace.setY(framebufferOutputRect.height() - (scissorRectInCanvasSpace.maxY() - framebufferOutputRect.y())); + else + scissorRectInCanvasSpace.setY(scissorRectInCanvasSpace.y() - framebufferOutputRect.y()); + return scissorRectInCanvasSpace; +} + +void CCDirectRenderer::decideRenderPassAllocationsForFrame(const CCRenderPassList& renderPassesInDrawOrder) +{ + HashMap<int, const CCRenderPass*> renderPassesInFrame; + for (size_t i = 0; i < renderPassesInDrawOrder.size(); ++i) + renderPassesInFrame.set(renderPassesInDrawOrder[i]->id(), renderPassesInDrawOrder[i]); + + Vector<int> passesToDelete; + HashMap<int, OwnPtr<CachedTexture> >::const_iterator passIterator; + for (passIterator = m_renderPassTextures.begin(); passIterator != m_renderPassTextures.end(); ++passIterator) { + const CCRenderPass* renderPassInFrame = renderPassesInFrame.get(passIterator->first); + if (!renderPassInFrame) { + passesToDelete.append(passIterator->first); + continue; + } + + const IntSize& requiredSize = renderPassTextureSize(renderPassInFrame); + GC3Denum requiredFormat = renderPassTextureFormat(renderPassInFrame); + CachedTexture* texture = passIterator->second.get(); + ASSERT(texture); + + if (texture->id() && (texture->size() != requiredSize || texture->format() != requiredFormat)) + texture->free(); + } + + // Delete RenderPass textures from the previous frame that will not be used again. + for (size_t i = 0; i < passesToDelete.size(); ++i) + m_renderPassTextures.remove(passesToDelete[i]); + + for (size_t i = 0; i < renderPassesInDrawOrder.size(); ++i) { + if (!m_renderPassTextures.contains(renderPassesInDrawOrder[i]->id())) { + OwnPtr<CachedTexture> texture = CachedTexture::create(m_resourceProvider); + m_renderPassTextures.set(renderPassesInDrawOrder[i]->id(), texture.release()); + } + } +} + +void CCDirectRenderer::drawFrame(const CCRenderPassList& renderPassesInDrawOrder, const CCRenderPassIdHashMap& renderPassesById) +{ + const CCRenderPass* rootRenderPass = renderPassesInDrawOrder.last(); + ASSERT(rootRenderPass); + + DrawingFrame frame; + frame.renderPassesById = &renderPassesById; + frame.rootRenderPass = rootRenderPass; + frame.rootDamageRect = capabilities().usingPartialSwap ? rootRenderPass->damageRect() : rootRenderPass->outputRect(); + frame.rootDamageRect.intersect(IntRect(IntPoint::zero(), viewportSize())); + + beginDrawingFrame(frame); + for (size_t i = 0; i < renderPassesInDrawOrder.size(); ++i) + drawRenderPass(frame, renderPassesInDrawOrder[i]); + finishDrawingFrame(frame); +} + +void CCDirectRenderer::drawRenderPass(DrawingFrame& frame, const CCRenderPass* renderPass) +{ + if (!useRenderPass(frame, renderPass)) + return; + + frame.scissorRectInRenderPassSpace = frame.currentRenderPass->outputRect(); + if (frame.rootDamageRect != frame.rootRenderPass->outputRect()) { + WebTransformationMatrix inverseTransformToRoot = frame.currentRenderPass->transformToRootTarget().inverse(); + frame.scissorRectInRenderPassSpace.intersect(CCMathUtil::projectClippedRect(inverseTransformToRoot, frame.rootDamageRect)); + } + + enableScissorTestRect(moveScissorToWindowSpace(frame, frame.scissorRectInRenderPassSpace)); + clearFramebuffer(frame); + + const CCQuadList& quadList = renderPass->quadList(); + for (CCQuadList::constBackToFrontIterator it = quadList.backToFrontBegin(); it != quadList.backToFrontEnd(); ++it) { + FloatRect quadScissorRect = frame.scissorRectInRenderPassSpace; + quadScissorRect.intersect(it->get()->clippedRectInTarget()); + if (!quadScissorRect.isEmpty()) { + enableScissorTestRect(moveScissorToWindowSpace(frame, quadScissorRect)); + drawQuad(frame, it->get()); + } + } + + CachedTexture* texture = m_renderPassTextures.get(renderPass->id()); + if (texture) + texture->setIsComplete(!renderPass->hasOcclusionFromOutsideTargetSurface()); +} + +bool CCDirectRenderer::useRenderPass(DrawingFrame& frame, const CCRenderPass* renderPass) +{ + frame.currentRenderPass = renderPass; + frame.currentTexture = 0; + + if (renderPass == frame.rootRenderPass) { + bindFramebufferToOutputSurface(frame); + initializeMatrices(frame, renderPass->outputRect(), true); + setDrawViewportSize(renderPass->outputRect().size()); + return true; + } + + CachedTexture* texture = m_renderPassTextures.get(renderPass->id()); + ASSERT(texture); + if (!texture->id() && !texture->allocate(CCRenderer::ImplPool, renderPassTextureSize(renderPass), renderPassTextureFormat(renderPass), CCResourceProvider::TextureUsageFramebuffer)) + return false; + + return bindFramebufferToTexture(frame, texture, renderPass->outputRect()); +} + +bool CCDirectRenderer::haveCachedResourcesForRenderPassId(int id) const +{ + CachedTexture* texture = m_renderPassTextures.get(id); + return texture && texture->id() && texture->isComplete(); +} + +// static +IntSize CCDirectRenderer::renderPassTextureSize(const CCRenderPass* pass) +{ + return pass->outputRect().size(); +} + +// static +GC3Denum CCDirectRenderer::renderPassTextureFormat(const CCRenderPass*) +{ + return GraphicsContext3D::RGBA; +} + +} diff --git a/cc/CCDirectRenderer.h b/cc/CCDirectRenderer.h new file mode 100644 index 0000000..097d5fa --- /dev/null +++ b/cc/CCDirectRenderer.h @@ -0,0 +1,106 @@ +// Copyright 2012 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. + +#ifndef CCDirectRenderer_h +#define CCDirectRenderer_h + +#include "CCRenderer.h" +#include "CCResourceProvider.h" +#include "CCScopedTexture.h" + +namespace WebCore { + +class CCResourceProvider; + +// This is the base class for code shared between the GL and software +// renderer implementations. "Direct" refers to the fact that it does not +// delegate rendering to another compositor. +class CCDirectRenderer : public CCRenderer { + WTF_MAKE_NONCOPYABLE(CCDirectRenderer); +public: + virtual ~CCDirectRenderer() { } + + CCResourceProvider* resourceProvider() const { return m_resourceProvider; } + + virtual void decideRenderPassAllocationsForFrame(const CCRenderPassList& renderPassesInDrawOrder) OVERRIDE; + virtual bool haveCachedResourcesForRenderPassId(int id) const OVERRIDE; + virtual void drawFrame(const CCRenderPassList& renderPassesInDrawOrder, const CCRenderPassIdHashMap& renderPassesById) OVERRIDE; + +protected: + CCDirectRenderer(CCRendererClient* client, CCResourceProvider* resourceProvider) + : CCRenderer(client) + , m_resourceProvider(resourceProvider) + { + } + + struct DrawingFrame { + const CCRenderPassIdHashMap* renderPassesById; + const CCRenderPass* rootRenderPass; + const CCRenderPass* currentRenderPass; + const CCScopedTexture* currentTexture; + + FloatRect rootDamageRect; + + WebKit::WebTransformationMatrix projectionMatrix; + WebKit::WebTransformationMatrix windowMatrix; + bool flippedY; + FloatRect scissorRectInRenderPassSpace; + + DrawingFrame() + : rootRenderPass(0) + , currentRenderPass(0) + , currentTexture(0) + , flippedY(false) + { } + }; + + class CachedTexture : public CCScopedTexture { + WTF_MAKE_NONCOPYABLE(CachedTexture); + public: + static PassOwnPtr<CachedTexture> create(CCResourceProvider* resourceProvider) { return adoptPtr(new CachedTexture(resourceProvider)); } + virtual ~CachedTexture() { } + + bool isComplete() const { return m_isComplete; } + void setIsComplete(bool isComplete) { m_isComplete = isComplete; } + + protected: + explicit CachedTexture(CCResourceProvider* resourceProvider) + : CCScopedTexture(resourceProvider) + , m_isComplete(false) + { + } + + private: + bool m_isComplete; + }; + + static FloatRect quadVertexRect(); + static void quadRectTransform(WebKit::WebTransformationMatrix* quadRectTransform, const WebKit::WebTransformationMatrix& quadTransform, const FloatRect& quadRect); + static void initializeMatrices(DrawingFrame&, const IntRect& drawRect, bool flipY); + static IntRect moveScissorToWindowSpace(const DrawingFrame&, FloatRect scissorRect); + + bool haveCachedResources(int renderPassId) const; + static IntSize renderPassTextureSize(const CCRenderPass*); + static GC3Denum renderPassTextureFormat(const CCRenderPass*); + + void drawRenderPass(DrawingFrame&, const CCRenderPass*); + bool useRenderPass(DrawingFrame&, const CCRenderPass*); + + virtual void bindFramebufferToOutputSurface(DrawingFrame&) = 0; + virtual bool bindFramebufferToTexture(DrawingFrame&, const CCScopedTexture*, const IntRect& framebufferRect) = 0; + virtual void setDrawViewportSize(const IntSize&) = 0; + virtual void enableScissorTestRect(const IntRect& scissorRect) = 0; + virtual void disableScissorTest() = 0; + virtual void clearFramebuffer(DrawingFrame&) = 0; + virtual void drawQuad(DrawingFrame&, const CCDrawQuad*) = 0; + virtual void beginDrawingFrame(DrawingFrame&) = 0; + virtual void finishDrawingFrame(DrawingFrame&) = 0; + + HashMap<int, OwnPtr<CachedTexture> > m_renderPassTextures; + CCResourceProvider* m_resourceProvider; +}; + +} + +#endif // CCDirectRenderer_h diff --git a/cc/CCDrawQuad.cpp b/cc/CCDrawQuad.cpp new file mode 100644 index 0000000..ad14b84 --- /dev/null +++ b/cc/CCDrawQuad.cpp @@ -0,0 +1,85 @@ +// Copyright 2012 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 "CCDrawQuad.h" + +#include "CCCheckerboardDrawQuad.h" +#include "CCDebugBorderDrawQuad.h" +#include "CCIOSurfaceDrawQuad.h" +#include "CCRenderPassDrawQuad.h" +#include "CCSolidColorDrawQuad.h" +#include "CCStreamVideoDrawQuad.h" +#include "CCTextureDrawQuad.h" +#include "CCTileDrawQuad.h" +#include "CCYUVVideoDrawQuad.h" +#include "IntRect.h" + +namespace WebCore { + +CCDrawQuad::CCDrawQuad(const CCSharedQuadState* sharedQuadState, Material material, const IntRect& quadRect) + : m_sharedQuadState(sharedQuadState) + , m_sharedQuadStateId(sharedQuadState->id) + , m_material(material) + , m_quadRect(quadRect) + , m_quadVisibleRect(quadRect) + , m_quadOpaque(true) + , m_needsBlending(false) +{ + ASSERT(m_sharedQuadState); + ASSERT(m_material != Invalid); +} + +IntRect CCDrawQuad::opaqueRect() const +{ + if (opacity() != 1) + return IntRect(); + if (m_sharedQuadState->opaque && m_quadOpaque) + return m_quadRect; + return m_opaqueRect; +} + +void CCDrawQuad::setQuadVisibleRect(const IntRect& quadVisibleRect) +{ + IntRect intersection = quadVisibleRect; + intersection.intersect(m_quadRect); + m_quadVisibleRect = intersection; +} + +unsigned CCDrawQuad::size() const +{ + switch (material()) { + case Checkerboard: + return sizeof(CCCheckerboardDrawQuad); + case DebugBorder: + return sizeof(CCDebugBorderDrawQuad); + case IOSurfaceContent: + return sizeof(CCIOSurfaceDrawQuad); + case TextureContent: + return sizeof(CCTextureDrawQuad); + case SolidColor: + return sizeof(CCSolidColorDrawQuad); + case TiledContent: + return sizeof(CCTileDrawQuad); + case StreamVideoContent: + return sizeof(CCStreamVideoDrawQuad); + case RenderPass: + return sizeof(CCRenderPassDrawQuad); + case YUVVideoContent: + return sizeof(CCYUVVideoDrawQuad); + case Invalid: + break; + } + + CRASH(); + return sizeof(CCDrawQuad); +} + +void CCDrawQuad::setSharedQuadState(const CCSharedQuadState* sharedQuadState) +{ + m_sharedQuadState = sharedQuadState; + m_sharedQuadStateId = sharedQuadState->id; +} + +} diff --git a/cc/CCDrawQuad.h b/cc/CCDrawQuad.h new file mode 100644 index 0000000..ab603fc --- /dev/null +++ b/cc/CCDrawQuad.h @@ -0,0 +1,94 @@ +// Copyright 2012 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. + +#ifndef CCDrawQuad_h +#define CCDrawQuad_h + +#include "CCSharedQuadState.h" + +namespace WebCore { + +// WARNING! All CCXYZDrawQuad classes must remain PODs (plain old data). +// They are intended to be "serializable" by copying their raw bytes, so they +// must not contain any non-bit-copyable member variables! +// +// Furthermore, the class members need to be packed so they are aligned +// properly and don't have paddings/gaps, otherwise memory check tools +// like Valgrind will complain about uninitialized memory usage when +// transferring these classes over the wire. +#pragma pack(push, 4) + +// CCDrawQuad is a bag of data used for drawing a quad. Because different +// materials need different bits of per-quad data to render, classes that derive +// from CCDrawQuad store additional data in their derived instance. The Material +// enum is used to "safely" downcast to the derived class. +class CCDrawQuad { +public: + enum Material { + Invalid, + Checkerboard, + DebugBorder, + IOSurfaceContent, + RenderPass, + TextureContent, + SolidColor, + TiledContent, + YUVVideoContent, + StreamVideoContent, + }; + + IntRect quadRect() const { return m_quadRect; } + const WebKit::WebTransformationMatrix& quadTransform() const { return m_sharedQuadState->quadTransform; } + IntRect visibleContentRect() const { return m_sharedQuadState->visibleContentRect; } + IntRect clippedRectInTarget() const { return m_sharedQuadState->clippedRectInTarget; } + float opacity() const { return m_sharedQuadState->opacity; } + // For the purposes of blending, what part of the contents of this quad are opaque? + IntRect opaqueRect() const; + bool needsBlending() const { return m_needsBlending || !opaqueRect().contains(m_quadVisibleRect); } + + // Allows changing the rect that gets drawn to make it smaller. Parameter passed + // in will be clipped to quadRect(). + void setQuadVisibleRect(const IntRect&); + IntRect quadVisibleRect() const { return m_quadVisibleRect; } + bool isDebugQuad() const { return m_material == DebugBorder; } + + Material material() const { return m_material; } + + // Returns transfer size of this object based on the derived class (by + // looking at the material type). + unsigned size() const; + + const CCSharedQuadState* sharedQuadState() const { return m_sharedQuadState; } + int sharedQuadStateId() const { return m_sharedQuadStateId; } + void setSharedQuadState(const CCSharedQuadState*); + +protected: + CCDrawQuad(const CCSharedQuadState*, Material, const IntRect&); + + // Stores state common to a large bundle of quads; kept separate for memory + // efficiency. There is special treatment to reconstruct these pointers + // during serialization. + const CCSharedQuadState* m_sharedQuadState; + int m_sharedQuadStateId; + + Material m_material; + IntRect m_quadRect; + IntRect m_quadVisibleRect; + + // By default, the shared quad state determines whether or not this quad is + // opaque or needs blending. Derived classes can override with these + // variables. + bool m_quadOpaque; + bool m_needsBlending; + + // Be default, this rect is empty. It is used when the shared quad state and above + // variables determine that the quad is not fully opaque but may be partially opaque. + IntRect m_opaqueRect; +}; + +#pragma pack(pop) + +} + +#endif diff --git a/cc/CCFontAtlas.cpp b/cc/CCFontAtlas.cpp new file mode 100644 index 0000000..44695e1 --- /dev/null +++ b/cc/CCFontAtlas.cpp @@ -0,0 +1,70 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) +#include "CCFontAtlas.h" + +#include "CCProxy.h" +#include "SkCanvas.h" + +namespace WebCore { + +using namespace std; + +CCFontAtlas::CCFontAtlas(SkBitmap bitmap, IntRect asciiToRectTable[128], int fontHeight) + : m_atlas(bitmap) + , m_fontHeight(fontHeight) +{ + for (size_t i = 0; i < 128; ++i) + m_asciiToRectTable[i] = asciiToRectTable[i]; +} + +CCFontAtlas::~CCFontAtlas() +{ +} + +void CCFontAtlas::drawText(SkCanvas* canvas, const SkPaint& paint, const String& text, const IntPoint& destPosition, const IntSize& clip) const +{ + ASSERT(CCProxy::isImplThread()); + + Vector<String> lines; + text.split('\n', lines); + + IntPoint position = destPosition; + for (size_t i = 0; i < lines.size(); ++i) { + drawOneLineOfTextInternal(canvas, paint, lines[i], position); + position.setY(position.y() + m_fontHeight); + if (position.y() > clip.height()) + return; + } +} + +void CCFontAtlas::drawOneLineOfTextInternal(SkCanvas* canvas, const SkPaint& paint, const String& textLine, const IntPoint& destPosition) const +{ + ASSERT(CCProxy::isImplThread()); + + IntPoint position = destPosition; + for (unsigned i = 0; i < textLine.length(); ++i) { + // If the ASCII code is out of bounds, then index 0 is used, which is just a plain rectangle glyph. + int asciiIndex = (textLine[i] < 128) ? textLine[i] : 0; + IntRect glyphBounds = m_asciiToRectTable[asciiIndex]; + SkIRect source = SkIRect::MakeXYWH(glyphBounds.x(), glyphBounds.y(), glyphBounds.width(), glyphBounds.height()); + canvas->drawBitmapRect(m_atlas, &source, SkRect::MakeXYWH(position.x(), position.y(), glyphBounds.width(), glyphBounds.height()), &paint); + position.setX(position.x() + glyphBounds.width()); + } +} + +void CCFontAtlas::drawDebugAtlas(SkCanvas* canvas, const IntPoint& destPosition) const +{ + ASSERT(CCProxy::isImplThread()); + + SkIRect source = SkIRect::MakeWH(m_atlas.width(), m_atlas.height()); + canvas->drawBitmapRect(m_atlas, &source, SkRect::MakeXYWH(destPosition.x(), destPosition.y(), m_atlas.width(), m_atlas.height())); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCFontAtlas.h b/cc/CCFontAtlas.h new file mode 100644 index 0000000..df4818f --- /dev/null +++ b/cc/CCFontAtlas.h @@ -0,0 +1,65 @@ +// Copyright 2012 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. + +#ifndef CCFontAtlas_h +#define CCFontAtlas_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "IntRect.h" +#include "SkBitmap.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/text/WTFString.h> + +class SkCanvas; + +namespace WebCore { + +class Color; +class FontDescription; +class GraphicsContext; +class IntPoint; +class IntSize; + +// This class provides basic ability to draw text onto the heads-up display. +class CCFontAtlas { + WTF_MAKE_NONCOPYABLE(CCFontAtlas); +public: + static PassOwnPtr<CCFontAtlas> create(SkBitmap bitmap, IntRect asciiToRectTable[128], int fontHeight) + { + return adoptPtr(new CCFontAtlas(bitmap, asciiToRectTable, fontHeight)); + } + ~CCFontAtlas(); + + // Draws multiple lines of text where each line of text is separated by '\n'. + // - Correct glyphs will be drawn for ASCII codes in the range 32-127; any characters + // outside that range will be displayed as a default rectangle glyph. + // - IntSize clip is used to avoid wasting time drawing things that are outside the + // target canvas bounds. + // - Should only be called only on the impl thread. + void drawText(SkCanvas*, const SkPaint&, const String& text, const IntPoint& destPosition, const IntSize& clip) const; + + // Draws the entire atlas at the specified position, just for debugging purposes. + void drawDebugAtlas(SkCanvas*, const IntPoint& destPosition) const; + +private: + CCFontAtlas(SkBitmap, IntRect asciiToRectTable[128], int fontHeight); + + void drawOneLineOfTextInternal(SkCanvas*, const SkPaint&, const String&, const IntPoint& destPosition) const; + + // The actual texture atlas containing all the pre-rendered glyphs. + SkBitmap m_atlas; + + // The look-up tables mapping ascii characters to their IntRect locations on the atlas. + IntRect m_asciiToRectTable[128]; + + int m_fontHeight; +}; + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/CCFrameRateController.cpp b/cc/CCFrameRateController.cpp new file mode 100644 index 0000000..6f021f1 --- /dev/null +++ b/cc/CCFrameRateController.cpp @@ -0,0 +1,142 @@ +// 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 "config.h" + +#include "CCFrameRateController.h" + +#include "CCDelayBasedTimeSource.h" +#include "CCTimeSource.h" +#include "TraceEvent.h" +#include <wtf/CurrentTime.h> + +namespace WebCore { + +class CCFrameRateControllerTimeSourceAdapter : public CCTimeSourceClient { +public: + static PassOwnPtr<CCFrameRateControllerTimeSourceAdapter> create(CCFrameRateController* frameRateController) + { + return adoptPtr(new CCFrameRateControllerTimeSourceAdapter(frameRateController)); + } + virtual ~CCFrameRateControllerTimeSourceAdapter() { } + + virtual void onTimerTick() OVERRIDE { m_frameRateController->onTimerTick(); } +private: + explicit CCFrameRateControllerTimeSourceAdapter(CCFrameRateController* frameRateController) + : m_frameRateController(frameRateController) { } + + CCFrameRateController* m_frameRateController; +}; + +CCFrameRateController::CCFrameRateController(PassRefPtr<CCTimeSource> timer) + : m_client(0) + , m_numFramesPending(0) + , m_maxFramesPending(0) + , m_timeSource(timer) + , m_active(false) + , m_isTimeSourceThrottling(true) +{ + m_timeSourceClientAdapter = CCFrameRateControllerTimeSourceAdapter::create(this); + m_timeSource->setClient(m_timeSourceClientAdapter.get()); +} + +CCFrameRateController::CCFrameRateController(CCThread* thread) + : m_client(0) + , m_numFramesPending(0) + , m_maxFramesPending(0) + , m_active(false) + , m_isTimeSourceThrottling(false) +{ + m_manualTicker = adoptPtr(new CCTimer(thread, this)); +} + +CCFrameRateController::~CCFrameRateController() +{ + if (m_isTimeSourceThrottling) + m_timeSource->setActive(false); +} + +void CCFrameRateController::setActive(bool active) +{ + TRACE_EVENT1("cc", "CCFrameRateController::setActive", "active", active); + if (m_active == active) + return; + m_active = active; + + if (m_isTimeSourceThrottling) + m_timeSource->setActive(active); + else { + if (active) + postManualTick(); + else + m_manualTicker->stop(); + } +} + +void CCFrameRateController::setMaxFramesPending(int maxFramesPending) +{ + m_maxFramesPending = maxFramesPending; +} + +void CCFrameRateController::setTimebaseAndInterval(double timebase, double intervalSeconds) +{ + if (m_isTimeSourceThrottling) + m_timeSource->setTimebaseAndInterval(timebase, intervalSeconds); +} + +void CCFrameRateController::onTimerTick() +{ + ASSERT(m_active); + + // Don't forward the tick if we have too many frames in flight. + if (m_maxFramesPending && m_numFramesPending >= m_maxFramesPending) { + TRACE_EVENT0("cc", "CCFrameRateController::onTimerTickButMaxFramesPending"); + return; + } + + if (m_client) + m_client->vsyncTick(); + + if (!m_isTimeSourceThrottling + && (!m_maxFramesPending || m_numFramesPending < m_maxFramesPending)) + postManualTick(); +} + +void CCFrameRateController::postManualTick() +{ + if (m_active) + m_manualTicker->startOneShot(0); +} + +void CCFrameRateController::onTimerFired() +{ + onTimerTick(); +} + +void CCFrameRateController::didBeginFrame() +{ + m_numFramesPending++; +} + +void CCFrameRateController::didFinishFrame() +{ + m_numFramesPending--; + if (!m_isTimeSourceThrottling) + postManualTick(); +} + +void CCFrameRateController::didAbortAllPendingFrames() +{ + m_numFramesPending = 0; +} + +double CCFrameRateController::nextTickTimeIfActivated() +{ + if (m_isTimeSourceThrottling) + return m_timeSource->nextTickTimeIfActivated(); + + return monotonicallyIncreasingTime(); +} + +} diff --git a/cc/CCFrameRateController.h b/cc/CCFrameRateController.h new file mode 100644 index 0000000..0aa3fdf --- /dev/null +++ b/cc/CCFrameRateController.h @@ -0,0 +1,76 @@ +// 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. + +#ifndef CCFrameRateController_h +#define CCFrameRateController_h + +#include "CCTimer.h" + +#include <wtf/Deque.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCThread; +class CCTimeSource; + +class CCFrameRateControllerClient { +public: + virtual void vsyncTick() = 0; + +protected: + virtual ~CCFrameRateControllerClient() { } +}; + +class CCFrameRateControllerTimeSourceAdapter; + +class CCFrameRateController : public CCTimerClient { +public: + explicit CCFrameRateController(PassRefPtr<CCTimeSource>); + // Alternate form of CCFrameRateController with unthrottled frame-rate. + explicit CCFrameRateController(CCThread*); + virtual ~CCFrameRateController(); + + void setClient(CCFrameRateControllerClient* client) { m_client = client; } + + void setActive(bool); + + // Use the following methods to adjust target frame rate. + // + // Multiple frames can be in-progress, but for every didBeginFrame, a + // didFinishFrame should be posted. + // + // If the rendering pipeline crashes, call didAbortAllPendingFrames. + void didBeginFrame(); + void didFinishFrame(); + void didAbortAllPendingFrames(); + void setMaxFramesPending(int); // 0 for unlimited. + double nextTickTimeIfActivated(); + + void setTimebaseAndInterval(double timebase, double intervalSeconds); + +protected: + friend class CCFrameRateControllerTimeSourceAdapter; + void onTimerTick(); + + void postManualTick(); + + // CCTimerClient implementation (used for unthrottled frame-rate). + virtual void onTimerFired() OVERRIDE; + + CCFrameRateControllerClient* m_client; + int m_numFramesPending; + int m_maxFramesPending; + RefPtr<CCTimeSource> m_timeSource; + OwnPtr<CCFrameRateControllerTimeSourceAdapter> m_timeSourceClientAdapter; + bool m_active; + + // Members for unthrottled frame-rate. + bool m_isTimeSourceThrottling; + OwnPtr<CCTimer> m_manualTicker; +}; + +} +#endif // CCFrameRateController_h diff --git a/cc/CCFrameRateControllerTest.cpp b/cc/CCFrameRateControllerTest.cpp new file mode 100644 index 0000000..721db12 --- /dev/null +++ b/cc/CCFrameRateControllerTest.cpp @@ -0,0 +1,169 @@ +// 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 "config.h" + +#include "CCFrameRateController.h" + +#include "CCSchedulerTestCommon.h" +#include <gtest/gtest.h> + +using namespace WTF; +using namespace WebCore; +using namespace WebKitTests; + +namespace { + +class FakeCCFrameRateControllerClient : public WebCore::CCFrameRateControllerClient { +public: + FakeCCFrameRateControllerClient() { reset(); } + + void reset() { m_vsyncTicked = false; } + bool vsyncTicked() const { return m_vsyncTicked; } + + virtual void vsyncTick() { m_vsyncTicked = true; } + +protected: + bool m_vsyncTicked; +}; + + +TEST(CCFrameRateControllerTest, TestFrameThrottling_ImmediateAck) +{ + FakeCCThread thread; + FakeCCFrameRateControllerClient client; + RefPtr<FakeCCDelayBasedTimeSource> timeSource = FakeCCDelayBasedTimeSource::create(1.0 / 60.0, &thread); + CCFrameRateController controller(timeSource); + + controller.setClient(&client); + controller.setActive(true); + + double elapsed = 0; // Muck around with time a bit + + // Trigger one frame, make sure the vsync callback is called + elapsed += thread.pendingDelayMs() / 1000.0; + timeSource->setMonotonicTimeNow(elapsed); + thread.runPendingTask(); + EXPECT_TRUE(client.vsyncTicked()); + client.reset(); + + // Tell the controller we drew + controller.didBeginFrame(); + + // Tell the controller the frame ended 5ms later + timeSource->setMonotonicTimeNow(timeSource->monotonicTimeNow() + 0.005); + controller.didFinishFrame(); + + // Trigger another frame, make sure vsync runs again + elapsed += thread.pendingDelayMs() / 1000.0; + EXPECT_TRUE(elapsed >= timeSource->monotonicTimeNow()); // Sanity check that previous code didn't move time backward. + timeSource->setMonotonicTimeNow(elapsed); + thread.runPendingTask(); + EXPECT_TRUE(client.vsyncTicked()); +} + +TEST(CCFrameRateControllerTest, TestFrameThrottling_TwoFramesInFlight) +{ + FakeCCThread thread; + FakeCCFrameRateControllerClient client; + RefPtr<FakeCCDelayBasedTimeSource> timeSource = FakeCCDelayBasedTimeSource::create(1.0 / 60.0, &thread); + CCFrameRateController controller(timeSource); + + controller.setClient(&client); + controller.setActive(true); + controller.setMaxFramesPending(2); + + double elapsed = 0; // Muck around with time a bit + + // Trigger one frame, make sure the vsync callback is called + elapsed += thread.pendingDelayMs() / 1000.0; + timeSource->setMonotonicTimeNow(elapsed); + thread.runPendingTask(); + EXPECT_TRUE(client.vsyncTicked()); + client.reset(); + + // Tell the controller we drew + controller.didBeginFrame(); + + // Trigger another frame, make sure vsync callback runs again + elapsed += thread.pendingDelayMs() / 1000.0; + EXPECT_TRUE(elapsed >= timeSource->monotonicTimeNow()); // Sanity check that previous code didn't move time backward. + timeSource->setMonotonicTimeNow(elapsed); + thread.runPendingTask(); + EXPECT_TRUE(client.vsyncTicked()); + client.reset(); + + // Tell the controller we drew, again. + controller.didBeginFrame(); + + // Trigger another frame. Since two frames are pending, we should not draw. + elapsed += thread.pendingDelayMs() / 1000.0; + EXPECT_TRUE(elapsed >= timeSource->monotonicTimeNow()); // Sanity check that previous code didn't move time backward. + timeSource->setMonotonicTimeNow(elapsed); + thread.runPendingTask(); + EXPECT_FALSE(client.vsyncTicked()); + + // Tell the controller the first frame ended 5ms later + timeSource->setMonotonicTimeNow(timeSource->monotonicTimeNow() + 0.005); + controller.didFinishFrame(); + + // Tick should not have been called + EXPECT_FALSE(client.vsyncTicked()); + + // Trigger yet another frame. Since one frames is pending, another vsync callback should run. + elapsed += thread.pendingDelayMs() / 1000.0; + EXPECT_TRUE(elapsed >= timeSource->monotonicTimeNow()); // Sanity check that previous code didn't move time backward. + timeSource->setMonotonicTimeNow(elapsed); + thread.runPendingTask(); + EXPECT_TRUE(client.vsyncTicked()); +} + +TEST(CCFrameRateControllerTest, TestFrameThrottling_Unthrottled) +{ + FakeCCThread thread; + FakeCCFrameRateControllerClient client; + CCFrameRateController controller(&thread); + + controller.setClient(&client); + controller.setMaxFramesPending(2); + + // setActive triggers 1st frame, make sure the vsync callback is called + controller.setActive(true); + thread.runPendingTask(); + EXPECT_TRUE(client.vsyncTicked()); + client.reset(); + + // Even if we don't call didBeginFrame, CCFrameRateController should + // still attempt to vsync tick multiple times until it does result in + // a didBeginFrame. + thread.runPendingTask(); + EXPECT_TRUE(client.vsyncTicked()); + client.reset(); + + thread.runPendingTask(); + EXPECT_TRUE(client.vsyncTicked()); + client.reset(); + + // didBeginFrame triggers 2nd frame, make sure the vsync callback is called + controller.didBeginFrame(); + thread.runPendingTask(); + EXPECT_TRUE(client.vsyncTicked()); + client.reset(); + + // didBeginFrame triggers 3rd frame (> maxFramesPending), make sure the vsync callback is NOT called + controller.didBeginFrame(); + thread.runPendingTask(); + EXPECT_FALSE(client.vsyncTicked()); + client.reset(); + + // Make sure there is no pending task since we can't do anything until we receive a didFinishFrame anyway. + EXPECT_FALSE(thread.hasPendingTask()); + + // didFinishFrame triggers a frame, make sure the vsync callback is called + controller.didFinishFrame(); + thread.runPendingTask(); + EXPECT_TRUE(client.vsyncTicked()); +} + +} diff --git a/cc/CCFrameRateCounter.cpp b/cc/CCFrameRateCounter.cpp new file mode 100644 index 0000000..bd5b140 --- /dev/null +++ b/cc/CCFrameRateCounter.cpp @@ -0,0 +1,130 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) +#include "CCFrameRateCounter.h" + +#include "CCProxy.h" +#include <public/Platform.h> +#include <wtf/CurrentTime.h> + +namespace WebCore { + +const double CCFrameRateCounter::kFrameTooFast = 1.0 / 70.0; // measured in seconds +const double CCFrameRateCounter::kFrameTooSlow = 1.0 / 12.0; +const double CCFrameRateCounter::kDroppedFrameTime = 1.0 / 50.0; + +// safeMod works on -1, returning m-1 in that case. +static inline int safeMod(int number, int modulus) +{ + return (number + modulus) % modulus; +} + +inline double CCFrameRateCounter::frameInterval(int frameNumber) const +{ + return m_timeStampHistory[frameIndex(frameNumber)] - + m_timeStampHistory[frameIndex(frameNumber - 1)]; +} + +inline int CCFrameRateCounter::frameIndex(int frameNumber) const +{ + return safeMod(frameNumber, kTimeStampHistorySize); +} + +CCFrameRateCounter::CCFrameRateCounter() + : m_currentFrameNumber(1) + , m_droppedFrameCount(0) +{ + m_timeStampHistory[0] = currentTime(); + m_timeStampHistory[1] = m_timeStampHistory[0]; + for (int i = 2; i < kTimeStampHistorySize; i++) + m_timeStampHistory[i] = 0; +} + +void CCFrameRateCounter::markBeginningOfFrame(double timestamp) +{ + m_timeStampHistory[frameIndex(m_currentFrameNumber)] = timestamp; + double frameIntervalSeconds = frameInterval(m_currentFrameNumber); + + if (CCProxy::hasImplThread() && m_currentFrameNumber > 0) { + double drawDelayMs = frameIntervalSeconds * 1000.0; + WebKit::Platform::current()->histogramCustomCounts("Renderer4.CompositorThreadImplDrawDelay", static_cast<int>(drawDelayMs), 1, 120, 60); + } + + if (!isBadFrameInterval(frameIntervalSeconds) && frameIntervalSeconds > kDroppedFrameTime) + ++m_droppedFrameCount; +} + +void CCFrameRateCounter::markEndOfFrame() +{ + m_currentFrameNumber += 1; +} + +bool CCFrameRateCounter::isBadFrameInterval(double intervalBetweenConsecutiveFrames) const +{ + bool schedulerAllowsDoubleFrames = !CCProxy::hasImplThread(); + bool intervalTooFast = schedulerAllowsDoubleFrames && intervalBetweenConsecutiveFrames < kFrameTooFast; + bool intervalTooSlow = intervalBetweenConsecutiveFrames > kFrameTooSlow; + return intervalTooFast || intervalTooSlow; +} + +bool CCFrameRateCounter::isBadFrame(int frameNumber) const +{ + return isBadFrameInterval(frameInterval(frameNumber)); +} + +void CCFrameRateCounter::getAverageFPSAndStandardDeviation(double& averageFPS, double& standardDeviation) const +{ + int frame = m_currentFrameNumber - 1; + averageFPS = 0; + int averageFPSCount = 0; + double fpsVarianceNumerator = 0; + + // Walk backwards through the samples looking for a run of good frame + // timings from which to compute the mean and standard deviation. + // + // Slow frames occur just because the user is inactive, and should be + // ignored. Fast frames are ignored if the scheduler is in single-thread + // mode in order to represent the true frame rate in spite of the fact that + // the first few swapbuffers happen instantly which skews the statistics + // too much for short lived animations. + // + // isBadFrame encapsulates the frame too slow/frame too fast logic. + while (1) { + if (!isBadFrame(frame)) { + averageFPSCount++; + double secForLastFrame = m_timeStampHistory[frameIndex(frame)] - + m_timeStampHistory[frameIndex(frame - 1)]; + double x = 1.0 / secForLastFrame; + double deltaFromAverage = x - averageFPS; + // Change with caution - numerics. http://en.wikipedia.org/wiki/Standard_deviation + averageFPS = averageFPS + deltaFromAverage / averageFPSCount; + fpsVarianceNumerator = fpsVarianceNumerator + deltaFromAverage * (x - averageFPS); + } + if (averageFPSCount && isBadFrame(frame)) { + // We've gathered a run of good samples, so stop. + break; + } + --frame; + if (frameIndex(frame) == frameIndex(m_currentFrameNumber) || frame < 0) { + // We've gone through all available historical data, so stop. + break; + } + } + + standardDeviation = sqrt(fpsVarianceNumerator / averageFPSCount); +} + +double CCFrameRateCounter::timeStampOfRecentFrame(int n) +{ + ASSERT(n >= 0 && n < kTimeStampHistorySize); + int desiredIndex = (frameIndex(m_currentFrameNumber) + n) % kTimeStampHistorySize; + return m_timeStampHistory[desiredIndex]; +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCFrameRateCounter.h b/cc/CCFrameRateCounter.h new file mode 100644 index 0000000..67763bc --- /dev/null +++ b/cc/CCFrameRateCounter.h @@ -0,0 +1,71 @@ +// Copyright 2012 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. + +#ifndef CCFrameRateCounter_h +#define CCFrameRateCounter_h + +#if USE(ACCELERATED_COMPOSITING) + +#include <wtf/Noncopyable.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +// This class maintains a history of timestamps, and provides functionality to +// intelligently compute average frames per second (and standard deviation). +class CCFrameRateCounter { + WTF_MAKE_NONCOPYABLE(CCFrameRateCounter); +public: + static PassOwnPtr<CCFrameRateCounter> create() + { + return adoptPtr(new CCFrameRateCounter()); + } + + void markBeginningOfFrame(double timestamp); + void markEndOfFrame(); + int currentFrameNumber() const { return m_currentFrameNumber; } + void getAverageFPSAndStandardDeviation(double& averageFPS, double& standardDeviation) const; + int timeStampHistorySize() const { return kTimeStampHistorySize; } + + // n = 0 returns the oldest frame retained in the history, + // while n = timeStampHistorySize() - 1 returns the timestamp most recent frame. + double timeStampOfRecentFrame(int /* n */); + + // This is a heuristic that can be used to ignore frames in a reasonable way. Returns + // true if the given frame interval is too fast or too slow, based on constant thresholds. + bool isBadFrameInterval(double intervalBetweenConsecutiveFrames) const; + + int droppedFrameCount() const { return m_droppedFrameCount; } + +private: + CCFrameRateCounter(); + + double frameInterval(int frameNumber) const; + int frameIndex(int frameNumber) const; + bool isBadFrame(int frameNumber) const; + + // Two thresholds (measured in seconds) that describe what is considered to be a "no-op frame" that should not be counted. + // - if the frame is too fast, then given our compositor implementation, the frame probably was a no-op and did not draw. + // - if the frame is too slow, then there is probably not animating content, so we should not pollute the average. + static const double kFrameTooFast; + static const double kFrameTooSlow; + + // If a frame takes longer than this threshold (measured in seconds) then we + // (naively) assume that it missed a screen refresh; that is, we dropped a frame. + // FIXME: Determine this threshold based on monitor refresh rate, crbug.com/138642. + static const double kDroppedFrameTime; + + static const int kTimeStampHistorySize = 120; + + int m_currentFrameNumber; + double m_timeStampHistory[kTimeStampHistorySize]; + + int m_droppedFrameCount; +}; + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/CCGestureCurve.h b/cc/CCGestureCurve.h new file mode 100644 index 0000000..a240e20 --- /dev/null +++ b/cc/CCGestureCurve.h @@ -0,0 +1,31 @@ +// Copyright 2012 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. + +#ifndef CCGestureCurve_h +#define CCGestureCurve_h + +namespace WebCore { + +class IntPoint; + +class CCGestureCurveTarget { +public: + virtual ~CCGestureCurveTarget() { } + + virtual void scrollBy(const IntPoint&) = 0; + // FIXME: add interfaces for scroll(), etc. +}; + +class CCGestureCurve { +public: + virtual ~CCGestureCurve() { } + + virtual const char* debugName() const = 0; + + virtual bool apply(double monotonicTime, CCGestureCurveTarget*) = 0; +}; + +} // namespace WebCore + +#endif diff --git a/cc/CCGraphicsContext.h b/cc/CCGraphicsContext.h new file mode 100644 index 0000000..592291c --- /dev/null +++ b/cc/CCGraphicsContext.h @@ -0,0 +1,21 @@ +// Copyright 2012 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. + +#ifndef CCGraphicsContext_h +#define CCGraphicsContext_h + +#include <public/WebCompositorOutputSurface.h> +#include <public/WebGraphicsContext3D.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +// FIXME: rename fully to CCOutputSurface. +typedef WebKit::WebCompositorOutputSurface CCGraphicsContext; + +} + +#endif // CCGraphicsContext_h diff --git a/cc/CCHeadsUpDisplayLayerImpl.cpp b/cc/CCHeadsUpDisplayLayerImpl.cpp new file mode 100644 index 0000000..dc6274d --- /dev/null +++ b/cc/CCHeadsUpDisplayLayerImpl.cpp @@ -0,0 +1,285 @@ +// Copyright 2012 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 "CCHeadsUpDisplayLayerImpl.h" + +#include "CCDebugRectHistory.h" +#include "CCFontAtlas.h" +#include "CCFrameRateCounter.h" +#include "CCLayerTreeHostImpl.h" +#include "CCQuadSink.h" +#include "CCTextureDrawQuad.h" +#include "Extensions3DChromium.h" +#include "GraphicsContext3D.h" +#include "SkBitmap.h" +#include "SkColorMatrixFilter.h" +#include "SkPaint.h" +#include "skia/ext/platform_canvas.h" +#include <wtf/text/WTFString.h> + +namespace WebCore { + +static inline SkPaint createPaint() +{ + // The SkCanvas is in RGBA but the shader is expecting BGRA, so we need to + // swizzle our colors when drawing to the SkCanvas. + SkColorMatrix swizzleMatrix; + for (int i = 0; i < 20; ++i) + swizzleMatrix.fMat[i] = 0; + swizzleMatrix.fMat[0 + 5 * 2] = 1; + swizzleMatrix.fMat[1 + 5 * 1] = 1; + swizzleMatrix.fMat[2 + 5 * 0] = 1; + swizzleMatrix.fMat[3 + 5 * 3] = 1; + + SkPaint paint; + paint.setColorFilter(new SkColorMatrixFilter(swizzleMatrix))->unref(); + return paint; +} + +CCHeadsUpDisplayLayerImpl::CCHeadsUpDisplayLayerImpl(int id) + : CCLayerImpl(id) +{ +} + +CCHeadsUpDisplayLayerImpl::~CCHeadsUpDisplayLayerImpl() +{ +} + +void CCHeadsUpDisplayLayerImpl::setFontAtlas(PassOwnPtr<CCFontAtlas> fontAtlas) +{ + m_fontAtlas = fontAtlas; +} + +void CCHeadsUpDisplayLayerImpl::willDraw(CCResourceProvider* resourceProvider) +{ + CCLayerImpl::willDraw(resourceProvider); + + if (!m_hudTexture) + m_hudTexture = CCScopedTexture::create(resourceProvider); + + // FIXME: Scale the HUD by deviceScale to make it more friendly under high DPI. + + if (m_hudTexture->size() != bounds()) + m_hudTexture->free(); + + if (!m_hudTexture->id()) + m_hudTexture->allocate(CCRenderer::ImplPool, bounds(), GraphicsContext3D::RGBA, CCResourceProvider::TextureUsageAny); +} + +void CCHeadsUpDisplayLayerImpl::appendQuads(CCQuadSink& quadSink, bool&) +{ + if (!m_hudTexture->id()) + return; + + CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState()); + + IntRect quadRect(IntPoint(), bounds()); + bool premultipliedAlpha = true; + FloatRect uvRect(0, 0, 1, 1); + bool flipped = false; + quadSink.append(CCTextureDrawQuad::create(sharedQuadState, quadRect, m_hudTexture->id(), premultipliedAlpha, uvRect, flipped)); +} + +void CCHeadsUpDisplayLayerImpl::updateHudTexture(CCResourceProvider* resourceProvider) +{ + if (!m_hudTexture->id()) + return; + + SkISize canvasSize; + if (m_hudCanvas) + canvasSize = m_hudCanvas->getDeviceSize(); + else + canvasSize.set(0, 0); + + if (canvasSize.fWidth != bounds().width() || canvasSize.fHeight != bounds().height() || !m_hudCanvas) + m_hudCanvas = adoptPtr(skia::CreateBitmapCanvas(bounds().width(), bounds().height(), false /* opaque */)); + + m_hudCanvas->clear(SkColorSetARGB(0, 0, 0, 0)); + drawHudContents(m_hudCanvas.get()); + + const SkBitmap* bitmap = &m_hudCanvas->getDevice()->accessBitmap(false); + SkAutoLockPixels locker(*bitmap); + + IntRect layerRect(IntPoint(), bounds()); + ASSERT(bitmap->config() == SkBitmap::kARGB_8888_Config); + resourceProvider->upload(m_hudTexture->id(), static_cast<const uint8_t*>(bitmap->getPixels()), layerRect, layerRect, IntSize()); +} + +void CCHeadsUpDisplayLayerImpl::didDraw(CCResourceProvider* resourceProvider) +{ + CCLayerImpl::didDraw(resourceProvider); + + if (!m_hudTexture->id()) + return; + + // FIXME: the following assert will not be true when sending resources to a + // parent compositor. We will probably need to hold on to m_hudTexture for + // longer, and have several HUD textures in the pipeline. + ASSERT(!resourceProvider->inUseByConsumer(m_hudTexture->id())); +} + +void CCHeadsUpDisplayLayerImpl::didLoseContext() +{ + m_hudTexture.clear(); +} + +void CCHeadsUpDisplayLayerImpl::drawHudContents(SkCanvas* canvas) +{ + const CCLayerTreeSettings& settings = layerTreeHostImpl()->settings(); + + if (settings.showPlatformLayerTree) { + SkPaint paint = createPaint(); + paint.setColor(SkColorSetARGB(192, 0, 0, 0)); + canvas->drawRect(SkRect::MakeXYWH(0, 0, bounds().width(), bounds().height()), paint); + } + + int fpsCounterHeight = 40; + int fpsCounterTop = 2; + int platformLayerTreeTop; + + if (settings.showFPSCounter) + platformLayerTreeTop = fpsCounterTop + fpsCounterHeight; + else + platformLayerTreeTop = 0; + + if (settings.showFPSCounter) + drawFPSCounter(canvas, layerTreeHostImpl()->fpsCounter(), fpsCounterTop, fpsCounterHeight); + + if (settings.showPlatformLayerTree && m_fontAtlas) { + String layerTree = layerTreeHostImpl()->layerTreeAsText(); + m_fontAtlas->drawText(canvas, createPaint(), layerTree, IntPoint(2, platformLayerTreeTop), bounds()); + } + + if (settings.showDebugRects()) + drawDebugRects(canvas, layerTreeHostImpl()->debugRectHistory()); +} + +void CCHeadsUpDisplayLayerImpl::drawFPSCounter(SkCanvas* canvas, CCFrameRateCounter* fpsCounter, int top, int height) +{ + float textWidth = 170; // so text fits on linux. + float graphWidth = fpsCounter->timeStampHistorySize(); + + // Draw the FPS text. + drawFPSCounterText(canvas, fpsCounter, top, textWidth, height); + + // Draw FPS graph. + const double loFPS = 0; + const double hiFPS = 80; + SkPaint paint = createPaint(); + paint.setColor(SkColorSetRGB(154, 205, 50)); + canvas->drawRect(SkRect::MakeXYWH(2 + textWidth, top, graphWidth, height / 2), paint); + + paint.setColor(SkColorSetRGB(255, 250, 205)); + canvas->drawRect(SkRect::MakeXYWH(2 + textWidth, top + height / 2, graphWidth, height / 2), paint); + + int graphLeft = static_cast<int>(textWidth + 3); + int x = 0; + double h = static_cast<double>(height - 2); + SkPath path; + for (int i = 0; i < fpsCounter->timeStampHistorySize() - 1; ++i) { + int j = i + 1; + double delta = fpsCounter->timeStampOfRecentFrame(j) - fpsCounter->timeStampOfRecentFrame(i); + + // Skip plotting this particular instantaneous frame rate if it is not likely to have been valid. + if (fpsCounter->isBadFrameInterval(delta)) { + x += 1; + continue; + } + + double fps = 1.0 / delta; + + // Clamp the FPS to the range we want to plot visually. + double p = 1 - ((fps - loFPS) / (hiFPS - loFPS)); + if (p < 0) + p = 0; + if (p > 1) + p = 1; + + // Plot this data point. + SkPoint cur = SkPoint::Make(graphLeft + x, 1 + top + p*h); + if (path.isEmpty()) + path.moveTo(cur); + else + path.lineTo(cur); + x += 1; + } + paint.setColor(SK_ColorRED); + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(1); + paint.setAntiAlias(true); + canvas->drawPath(path, paint); +} + +void CCHeadsUpDisplayLayerImpl::drawFPSCounterText(SkCanvas* canvas, CCFrameRateCounter* fpsCounter, int top, int width, int height) +{ + double averageFPS, stdDeviation; + fpsCounter->getAverageFPSAndStandardDeviation(averageFPS, stdDeviation); + + // Draw background. + SkPaint paint = createPaint(); + paint.setColor(SK_ColorBLACK); + canvas->drawRect(SkRect::MakeXYWH(2, top, width, height), paint); + + // Draw FPS text. + if (m_fontAtlas) + m_fontAtlas->drawText(canvas, createPaint(), String::format("FPS: %4.1f +/- %3.1f", averageFPS, stdDeviation), IntPoint(10, height / 3), IntSize(width, height)); +} + +void CCHeadsUpDisplayLayerImpl::drawDebugRects(SkCanvas* canvas, CCDebugRectHistory* debugRectHistory) +{ + const Vector<CCDebugRect>& debugRects = debugRectHistory->debugRects(); + + for (size_t i = 0; i < debugRects.size(); ++i) { + SkColor strokeColor = 0; + SkColor fillColor = 0; + + switch (debugRects[i].type) { + case PaintRectType: + // Paint rects in red + strokeColor = SkColorSetARGB(255, 255, 0, 0); + fillColor = SkColorSetARGB(30, 255, 0, 0); + break; + case PropertyChangedRectType: + // Property-changed rects in blue + strokeColor = SkColorSetARGB(255, 255, 0, 0); + fillColor = SkColorSetARGB(30, 0, 0, 255); + break; + case SurfaceDamageRectType: + // Surface damage rects in yellow-orange + strokeColor = SkColorSetARGB(255, 200, 100, 0); + fillColor = SkColorSetARGB(30, 200, 100, 0); + break; + case ReplicaScreenSpaceRectType: + // Screen space rects in green. + strokeColor = SkColorSetARGB(255, 100, 200, 0); + fillColor = SkColorSetARGB(30, 100, 200, 0); + break; + case ScreenSpaceRectType: + // Screen space rects in purple. + strokeColor = SkColorSetARGB(255, 100, 0, 200); + fillColor = SkColorSetARGB(10, 100, 0, 200); + break; + case OccludingRectType: + // Occluding rects in a reddish color. + strokeColor = SkColorSetARGB(255, 200, 0, 100); + fillColor = SkColorSetARGB(10, 200, 0, 100); + break; + } + + const FloatRect& rect = debugRects[i].rect; + SkRect skRect = SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()); + SkPaint paint = createPaint(); + paint.setColor(fillColor); + canvas->drawRect(skRect, paint); + + paint.setColor(strokeColor); + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(2); + canvas->drawRect(skRect, paint); + } +} + +} diff --git a/cc/CCHeadsUpDisplayLayerImpl.h b/cc/CCHeadsUpDisplayLayerImpl.h new file mode 100644 index 0000000..6ad825a --- /dev/null +++ b/cc/CCHeadsUpDisplayLayerImpl.h @@ -0,0 +1,56 @@ +// Copyright 2012 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. + +#ifndef CCHeadsUpDisplayLayerImpl_h +#define CCHeadsUpDisplayLayerImpl_h + +#include "CCFontAtlas.h" +#include "CCLayerImpl.h" +#include "CCScopedTexture.h" + +class SkCanvas; + +namespace WebCore { + +class CCDebugRectHistory; +class CCFontAtlas; +class CCFrameRateCounter; + +class CCHeadsUpDisplayLayerImpl : public CCLayerImpl { +public: + static PassOwnPtr<CCHeadsUpDisplayLayerImpl> create(int id) + { + return adoptPtr(new CCHeadsUpDisplayLayerImpl(id)); + } + virtual ~CCHeadsUpDisplayLayerImpl(); + + void setFontAtlas(PassOwnPtr<CCFontAtlas>); + + virtual void willDraw(CCResourceProvider*) OVERRIDE; + virtual void appendQuads(CCQuadSink&, bool& hadMissingTiles) OVERRIDE; + void updateHudTexture(CCResourceProvider*); + virtual void didDraw(CCResourceProvider*) OVERRIDE; + + virtual void didLoseContext() OVERRIDE; + + virtual bool layerIsAlwaysDamaged() const OVERRIDE { return true; } + +private: + explicit CCHeadsUpDisplayLayerImpl(int); + + virtual const char* layerTypeAsString() const OVERRIDE { return "HeadsUpDisplayLayer"; } + + void drawHudContents(SkCanvas*); + void drawFPSCounter(SkCanvas*, CCFrameRateCounter*, int top, int height); + void drawFPSCounterText(SkCanvas*, CCFrameRateCounter*, int top, int width, int height); + void drawDebugRects(SkCanvas*, CCDebugRectHistory*); + + OwnPtr<CCFontAtlas> m_fontAtlas; + OwnPtr<CCScopedTexture> m_hudTexture; + OwnPtr<SkCanvas> m_hudCanvas; +}; + +} + +#endif // CCHeadsUpDisplayLayerImpl_h diff --git a/cc/CCIOSurfaceDrawQuad.cpp b/cc/CCIOSurfaceDrawQuad.cpp new file mode 100644 index 0000000..3ba6318 --- /dev/null +++ b/cc/CCIOSurfaceDrawQuad.cpp @@ -0,0 +1,30 @@ +// Copyright 2012 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 "CCIOSurfaceDrawQuad.h" + +namespace WebCore { + +PassOwnPtr<CCIOSurfaceDrawQuad> CCIOSurfaceDrawQuad::create(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, const IntSize& ioSurfaceSize, unsigned ioSurfaceTextureId, Orientation orientation) +{ + return adoptPtr(new CCIOSurfaceDrawQuad(sharedQuadState, quadRect, ioSurfaceSize, ioSurfaceTextureId, orientation)); +} + +CCIOSurfaceDrawQuad::CCIOSurfaceDrawQuad(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, const IntSize& ioSurfaceSize, unsigned ioSurfaceTextureId, Orientation orientation) + : CCDrawQuad(sharedQuadState, CCDrawQuad::IOSurfaceContent, quadRect) + , m_ioSurfaceSize(ioSurfaceSize) + , m_ioSurfaceTextureId(ioSurfaceTextureId) + , m_orientation(orientation) +{ +} + +const CCIOSurfaceDrawQuad* CCIOSurfaceDrawQuad::materialCast(const CCDrawQuad* quad) +{ + ASSERT(quad->material() == CCDrawQuad::IOSurfaceContent); + return static_cast<const CCIOSurfaceDrawQuad*>(quad); +} + +} diff --git a/cc/CCIOSurfaceDrawQuad.h b/cc/CCIOSurfaceDrawQuad.h new file mode 100644 index 0000000..44b5e23 --- /dev/null +++ b/cc/CCIOSurfaceDrawQuad.h @@ -0,0 +1,42 @@ +// Copyright 2012 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. + +#ifndef CCIOSurfaceDrawQuad_h +#define CCIOSurfaceDrawQuad_h + +#include "CCDrawQuad.h" +#include "IntSize.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +#pragma pack(push, 4) + +class CCIOSurfaceDrawQuad : public CCDrawQuad { +public: + enum Orientation { + Flipped, + Unflipped + }; + + static PassOwnPtr<CCIOSurfaceDrawQuad> create(const CCSharedQuadState*, const IntRect&, const IntSize& ioSurfaceSize, unsigned ioSurfaceTextureId, Orientation); + + IntSize ioSurfaceSize() const { return m_ioSurfaceSize; } + unsigned ioSurfaceTextureId() const { return m_ioSurfaceTextureId; } + Orientation orientation() const { return m_orientation; } + + static const CCIOSurfaceDrawQuad* materialCast(const CCDrawQuad*); +private: + CCIOSurfaceDrawQuad(const CCSharedQuadState*, const IntRect&, const IntSize& ioSurfaceSize, unsigned ioSurfaceTextureId, Orientation); + + IntSize m_ioSurfaceSize; + unsigned m_ioSurfaceTextureId; + Orientation m_orientation; +}; + +#pragma pack(pop) + +} + +#endif diff --git a/cc/CCIOSurfaceLayerImpl.cpp b/cc/CCIOSurfaceLayerImpl.cpp new file mode 100644 index 0000000..4d070b0 --- /dev/null +++ b/cc/CCIOSurfaceLayerImpl.cpp @@ -0,0 +1,112 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCIOSurfaceLayerImpl.h" + +#include "CCGraphicsContext.h" +#include "CCIOSurfaceDrawQuad.h" +#include "CCLayerTreeHostImpl.h" +#include "CCQuadSink.h" +#include "CCRendererGL.h" // For the GLC() macro. +#include "Extensions3D.h" +#include "TextStream.h" +#include <public/WebGraphicsContext3D.h> + +namespace WebCore { + +CCIOSurfaceLayerImpl::CCIOSurfaceLayerImpl(int id) + : CCLayerImpl(id) + , m_ioSurfaceId(0) + , m_ioSurfaceChanged(false) + , m_ioSurfaceTextureId(0) +{ +} + +CCIOSurfaceLayerImpl::~CCIOSurfaceLayerImpl() +{ + if (!m_ioSurfaceTextureId) + return; + + CCGraphicsContext* context = layerTreeHostImpl()->context(); + // FIXME: Implement this path for software compositing. + WebKit::WebGraphicsContext3D* context3d = context->context3D(); + if (context3d) + context3d->deleteTexture(m_ioSurfaceTextureId); +} + +void CCIOSurfaceLayerImpl::willDraw(CCResourceProvider* resourceProvider) +{ + CCLayerImpl::willDraw(resourceProvider); + + if (m_ioSurfaceChanged) { + WebKit::WebGraphicsContext3D* context3d = resourceProvider->graphicsContext3D(); + if (!context3d) { + // FIXME: Implement this path for software compositing. + return; + } + + // FIXME: Do this in a way that we can track memory usage. + if (!m_ioSurfaceTextureId) + m_ioSurfaceTextureId = context3d->createTexture(); + + GLC(context3d, context3d->activeTexture(GraphicsContext3D::TEXTURE0)); + GLC(context3d, context3d->bindTexture(Extensions3D::TEXTURE_RECTANGLE_ARB, m_ioSurfaceTextureId)); + GLC(context3d, context3d->texParameteri(Extensions3D::TEXTURE_RECTANGLE_ARB, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR)); + GLC(context3d, context3d->texParameteri(Extensions3D::TEXTURE_RECTANGLE_ARB, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR)); + GLC(context3d, context3d->texParameteri(Extensions3D::TEXTURE_RECTANGLE_ARB, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE)); + GLC(context3d, context3d->texParameteri(Extensions3D::TEXTURE_RECTANGLE_ARB, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE)); + context3d->texImageIOSurface2DCHROMIUM(Extensions3D::TEXTURE_RECTANGLE_ARB, + m_ioSurfaceSize.width(), + m_ioSurfaceSize.height(), + m_ioSurfaceId, + 0); + // Do not check for error conditions. texImageIOSurface2DCHROMIUM is supposed to hold on to + // the last good IOSurface if the new one is already closed. This is only a possibility + // during live resizing of plugins. However, it seems that this is not sufficient to + // completely guard against garbage being drawn. If this is found to be a significant issue, + // it may be necessary to explicitly tell the embedder when to free the surfaces it has + // allocated. + m_ioSurfaceChanged = false; + } +} + +void CCIOSurfaceLayerImpl::appendQuads(CCQuadSink& quadSink, bool&) +{ + CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState()); + appendDebugBorderQuad(quadSink, sharedQuadState); + + IntRect quadRect(IntPoint(), contentBounds()); + quadSink.append(CCIOSurfaceDrawQuad::create(sharedQuadState, quadRect, m_ioSurfaceSize, m_ioSurfaceTextureId, CCIOSurfaceDrawQuad::Flipped)); +} + +void CCIOSurfaceLayerImpl::dumpLayerProperties(TextStream& ts, int indent) const +{ + writeIndent(ts, indent); + ts << "iosurface id: " << m_ioSurfaceId << " texture id: " << m_ioSurfaceTextureId; + CCLayerImpl::dumpLayerProperties(ts, indent); +} + +void CCIOSurfaceLayerImpl::didLoseContext() +{ + // We don't have a valid texture ID in the new context; however, + // the IOSurface is still valid. + m_ioSurfaceTextureId = 0; + m_ioSurfaceChanged = true; +} + +void CCIOSurfaceLayerImpl::setIOSurfaceProperties(unsigned ioSurfaceId, const IntSize& size) +{ + if (m_ioSurfaceId != ioSurfaceId) + m_ioSurfaceChanged = true; + + m_ioSurfaceId = ioSurfaceId; + m_ioSurfaceSize = size; +} +} + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCIOSurfaceLayerImpl.h b/cc/CCIOSurfaceLayerImpl.h new file mode 100644 index 0000000..9a10cbb --- /dev/null +++ b/cc/CCIOSurfaceLayerImpl.h @@ -0,0 +1,43 @@ +// Copyright 2012 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. + +#ifndef CCIOSurfaceLayerImpl_h +#define CCIOSurfaceLayerImpl_h + +#include "CCLayerImpl.h" +#include "IntSize.h" + +namespace WebCore { + +class CCIOSurfaceLayerImpl : public CCLayerImpl { +public: + static PassOwnPtr<CCIOSurfaceLayerImpl> create(int id) + { + return adoptPtr(new CCIOSurfaceLayerImpl(id)); + } + virtual ~CCIOSurfaceLayerImpl(); + + void setIOSurfaceProperties(unsigned ioSurfaceId, const IntSize&); + + virtual void appendQuads(CCQuadSink&, bool& hadMissingTiles) OVERRIDE; + + virtual void willDraw(CCResourceProvider*) OVERRIDE; + virtual void didLoseContext() OVERRIDE; + + virtual void dumpLayerProperties(TextStream&, int indent) const OVERRIDE; + +private: + explicit CCIOSurfaceLayerImpl(int); + + virtual const char* layerTypeAsString() const OVERRIDE { return "IOSurfaceLayer"; } + + unsigned m_ioSurfaceId; + IntSize m_ioSurfaceSize; + bool m_ioSurfaceChanged; + unsigned m_ioSurfaceTextureId; +}; + +} + +#endif // CCIOSurfaceLayerImpl_h diff --git a/cc/CCInputHandler.h b/cc/CCInputHandler.h new file mode 100644 index 0000000..db0cbf6 --- /dev/null +++ b/cc/CCInputHandler.h @@ -0,0 +1,85 @@ +// 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. + +#ifndef CCInputHandler_h +#define CCInputHandler_h + +#include <wtf/Noncopyable.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCActiveGestureAnimation; +class CCGestureCurveTarget; +class IntPoint; +class IntSize; + +// The CCInputHandler is a way for the embedders to interact with +// the impl thread side of the compositor implementation. +// +// There is one CCInputHandler for every CCLayerTreeHost. It is +// created and used only on the impl thread. +// +// The CCInputHandler is constructed with an InputHandlerClient, which is the +// interface by which the handler can manipulate the LayerTree. +class CCInputHandlerClient { + WTF_MAKE_NONCOPYABLE(CCInputHandlerClient); +public: + enum ScrollStatus { ScrollOnMainThread, ScrollStarted, ScrollIgnored }; + enum ScrollInputType { Gesture, Wheel }; + + // Selects a layer to be scrolled at a given point in window coordinates. + // Returns ScrollStarted if the layer at the coordinates can be scrolled, + // ScrollOnMainThread if the scroll event should instead be delegated to the + // main thread, or ScrollIgnored if there is nothing to be scrolled at the + // given coordinates. + virtual ScrollStatus scrollBegin(const IntPoint&, ScrollInputType) = 0; + + // Scroll the selected layer starting at the given window coordinate. If + // there is no room to move the layer in the requested direction, its first + // ancestor layer that can be scrolled will be moved instead. Should only be + // called if scrollBegin() returned ScrollStarted. + virtual void scrollBy(const IntPoint&, const IntSize&) = 0; + + // Stop scrolling the selected layer. Should only be called if scrollBegin() + // returned ScrollStarted. + virtual void scrollEnd() = 0; + + virtual void pinchGestureBegin() = 0; + virtual void pinchGestureUpdate(float magnifyDelta, const IntPoint& anchor) = 0; + virtual void pinchGestureEnd() = 0; + + virtual void startPageScaleAnimation(const IntSize& targetPosition, + bool anchorPoint, + float pageScale, + double startTime, + double duration) = 0; + + virtual CCActiveGestureAnimation* activeGestureAnimation() = 0; + virtual void setActiveGestureAnimation(PassOwnPtr<CCActiveGestureAnimation>) = 0; + + // Request another callback to CCInputHandler::animate(). + virtual void scheduleAnimation() = 0; + +protected: + CCInputHandlerClient() { } + virtual ~CCInputHandlerClient() { } +}; + +class CCInputHandler { + WTF_MAKE_NONCOPYABLE(CCInputHandler); +public: + static PassOwnPtr<CCInputHandler> create(CCInputHandlerClient*); + virtual ~CCInputHandler() { } + + virtual int identifier() const = 0; + virtual void animate(double monotonicTime) = 0; + +protected: + CCInputHandler() { } +}; + +} + +#endif diff --git a/cc/CCKeyframedAnimationCurve.cpp b/cc/CCKeyframedAnimationCurve.cpp new file mode 100644 index 0000000..d056b64 --- /dev/null +++ b/cc/CCKeyframedAnimationCurve.cpp @@ -0,0 +1,221 @@ +// Copyright 2012 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 "CCKeyframedAnimationCurve.h" + +#include <wtf/OwnPtr.h> + +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +namespace { + +template <class Keyframe> +void insertKeyframe(PassOwnPtr<Keyframe> popKeyframe, Vector<OwnPtr<Keyframe> >& keyframes) +{ + OwnPtr<Keyframe> keyframe = popKeyframe; + + // Usually, the keyframes will be added in order, so this loop would be unnecessary and + // we should skip it if possible. + if (!keyframes.isEmpty() && keyframe->time() < keyframes.last()->time()) { + for (size_t i = 0; i < keyframes.size(); ++i) { + if (keyframe->time() < keyframes[i]->time()) { + keyframes.insert(i, keyframe.release()); + return; + } + } + } + + keyframes.append(keyframe.release()); +} + +PassOwnPtr<CCTimingFunction> cloneTimingFunction(const CCTimingFunction* timingFunction) +{ + ASSERT(timingFunction); + OwnPtr<CCAnimationCurve> curve(timingFunction->clone()); + return adoptPtr(static_cast<CCTimingFunction*>(curve.leakPtr())); +} + +} // namespace + +CCKeyframe::CCKeyframe(double time, PassOwnPtr<CCTimingFunction> timingFunction) + : m_time(time) + , m_timingFunction(timingFunction) +{ +} + +CCKeyframe::~CCKeyframe() +{ +} + +double CCKeyframe::time() const +{ + return m_time; +} + +const CCTimingFunction* CCKeyframe::timingFunction() const +{ + return m_timingFunction.get(); +} + +PassOwnPtr<CCFloatKeyframe> CCFloatKeyframe::create(double time, float value, PassOwnPtr<CCTimingFunction> timingFunction) +{ + return adoptPtr(new CCFloatKeyframe(time, value, timingFunction)); +} + +CCFloatKeyframe::CCFloatKeyframe(double time, float value, PassOwnPtr<CCTimingFunction> timingFunction) + : CCKeyframe(time, timingFunction) + , m_value(value) +{ +} + +CCFloatKeyframe::~CCFloatKeyframe() +{ +} + +float CCFloatKeyframe::value() const +{ + return m_value; +} + +PassOwnPtr<CCFloatKeyframe> CCFloatKeyframe::clone() const +{ + return CCFloatKeyframe::create(time(), value(), timingFunction() ? cloneTimingFunction(timingFunction()) : nullptr); +} + +PassOwnPtr<CCTransformKeyframe> CCTransformKeyframe::create(double time, const WebKit::WebTransformOperations& value, PassOwnPtr<CCTimingFunction> timingFunction) +{ + return adoptPtr(new CCTransformKeyframe(time, value, timingFunction)); +} + +CCTransformKeyframe::CCTransformKeyframe(double time, const WebKit::WebTransformOperations& value, PassOwnPtr<CCTimingFunction> timingFunction) + : CCKeyframe(time, timingFunction) + , m_value(value) +{ +} + +CCTransformKeyframe::~CCTransformKeyframe() +{ +} + +const WebKit::WebTransformOperations& CCTransformKeyframe::value() const +{ + return m_value; +} + +PassOwnPtr<CCTransformKeyframe> CCTransformKeyframe::clone() const +{ + return CCTransformKeyframe::create(time(), value(), timingFunction() ? cloneTimingFunction(timingFunction()) : nullptr); +} + +PassOwnPtr<CCKeyframedFloatAnimationCurve> CCKeyframedFloatAnimationCurve::create() +{ + return adoptPtr(new CCKeyframedFloatAnimationCurve); +} + +CCKeyframedFloatAnimationCurve::CCKeyframedFloatAnimationCurve() +{ +} + +CCKeyframedFloatAnimationCurve::~CCKeyframedFloatAnimationCurve() +{ +} + +void CCKeyframedFloatAnimationCurve::addKeyframe(PassOwnPtr<CCFloatKeyframe> keyframe) +{ + insertKeyframe(keyframe, m_keyframes); +} + +double CCKeyframedFloatAnimationCurve::duration() const +{ + return m_keyframes.last()->time() - m_keyframes.first()->time(); +} + +PassOwnPtr<CCAnimationCurve> CCKeyframedFloatAnimationCurve::clone() const +{ + OwnPtr<CCKeyframedFloatAnimationCurve> toReturn(CCKeyframedFloatAnimationCurve::create()); + for (size_t i = 0; i < m_keyframes.size(); ++i) + toReturn->addKeyframe(m_keyframes[i]->clone()); + return toReturn.release(); +} + +float CCKeyframedFloatAnimationCurve::getValue(double t) const +{ + if (t <= m_keyframes.first()->time()) + return m_keyframes.first()->value(); + + if (t >= m_keyframes.last()->time()) + return m_keyframes.last()->value(); + + size_t i = 0; + for (; i < m_keyframes.size() - 1; ++i) { + if (t < m_keyframes[i+1]->time()) + break; + } + + float progress = static_cast<float>((t - m_keyframes[i]->time()) / (m_keyframes[i+1]->time() - m_keyframes[i]->time())); + + if (m_keyframes[i]->timingFunction()) + progress = m_keyframes[i]->timingFunction()->getValue(progress); + + return m_keyframes[i]->value() + (m_keyframes[i+1]->value() - m_keyframes[i]->value()) * progress; +} + +PassOwnPtr<CCKeyframedTransformAnimationCurve> CCKeyframedTransformAnimationCurve::create() +{ + return adoptPtr(new CCKeyframedTransformAnimationCurve); +} + +CCKeyframedTransformAnimationCurve::CCKeyframedTransformAnimationCurve() +{ +} + +CCKeyframedTransformAnimationCurve::~CCKeyframedTransformAnimationCurve() +{ +} + +void CCKeyframedTransformAnimationCurve::addKeyframe(PassOwnPtr<CCTransformKeyframe> keyframe) +{ + insertKeyframe(keyframe, m_keyframes); +} + +double CCKeyframedTransformAnimationCurve::duration() const +{ + return m_keyframes.last()->time() - m_keyframes.first()->time(); +} + +PassOwnPtr<CCAnimationCurve> CCKeyframedTransformAnimationCurve::clone() const +{ + OwnPtr<CCKeyframedTransformAnimationCurve> toReturn(CCKeyframedTransformAnimationCurve::create()); + for (size_t i = 0; i < m_keyframes.size(); ++i) + toReturn->addKeyframe(m_keyframes[i]->clone()); + return toReturn.release(); +} + +WebTransformationMatrix CCKeyframedTransformAnimationCurve::getValue(double t) const +{ + if (t <= m_keyframes.first()->time()) + return m_keyframes.first()->value().apply(); + + if (t >= m_keyframes.last()->time()) + return m_keyframes.last()->value().apply(); + + size_t i = 0; + for (; i < m_keyframes.size() - 1; ++i) { + if (t < m_keyframes[i+1]->time()) + break; + } + + double progress = (t - m_keyframes[i]->time()) / (m_keyframes[i+1]->time() - m_keyframes[i]->time()); + + if (m_keyframes[i]->timingFunction()) + progress = m_keyframes[i]->timingFunction()->getValue(progress); + + return m_keyframes[i+1]->value().blend(m_keyframes[i]->value(), progress); +} + +} // namespace WebCore diff --git a/cc/CCKeyframedAnimationCurve.h b/cc/CCKeyframedAnimationCurve.h new file mode 100644 index 0000000..01d0c9b --- /dev/null +++ b/cc/CCKeyframedAnimationCurve.h @@ -0,0 +1,111 @@ +// Copyright 2012 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. + +#ifndef CCKeyframedAnimationCurve_h +#define CCKeyframedAnimationCurve_h + +#include "CCAnimationCurve.h" +#include "CCTimingFunction.h" +#include <public/WebTransformOperations.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class CCKeyframe { +public: + double time() const; + const CCTimingFunction* timingFunction() const; + +protected: + CCKeyframe(double time, PassOwnPtr<CCTimingFunction>); + virtual ~CCKeyframe(); + +private: + double m_time; + OwnPtr<CCTimingFunction> m_timingFunction; +}; + +class CCFloatKeyframe : public CCKeyframe { +public: + static PassOwnPtr<CCFloatKeyframe> create(double time, float value, PassOwnPtr<CCTimingFunction>); + virtual ~CCFloatKeyframe(); + + float value() const; + + PassOwnPtr<CCFloatKeyframe> clone() const; + +private: + CCFloatKeyframe(double time, float value, PassOwnPtr<CCTimingFunction>); + + float m_value; +}; + +class CCTransformKeyframe : public CCKeyframe { +public: + static PassOwnPtr<CCTransformKeyframe> create(double time, const WebKit::WebTransformOperations& value, PassOwnPtr<CCTimingFunction>); + virtual ~CCTransformKeyframe(); + + const WebKit::WebTransformOperations& value() const; + + PassOwnPtr<CCTransformKeyframe> clone() const; + +private: + CCTransformKeyframe(double time, const WebKit::WebTransformOperations& value, PassOwnPtr<CCTimingFunction>); + + WebKit::WebTransformOperations m_value; +}; + +class CCKeyframedFloatAnimationCurve : public CCFloatAnimationCurve { +public: + // It is required that the keyframes be sorted by time. + static PassOwnPtr<CCKeyframedFloatAnimationCurve> create(); + + virtual ~CCKeyframedFloatAnimationCurve(); + + void addKeyframe(PassOwnPtr<CCFloatKeyframe>); + + // CCAnimationCurve implementation + virtual double duration() const OVERRIDE; + virtual PassOwnPtr<CCAnimationCurve> clone() const OVERRIDE; + + // CCFloatAnimationCurve implementation + virtual float getValue(double t) const OVERRIDE; + +private: + CCKeyframedFloatAnimationCurve(); + + // Always sorted in order of increasing time. No two keyframes have the + // same time. + Vector<OwnPtr<CCFloatKeyframe> > m_keyframes; +}; + +class CCKeyframedTransformAnimationCurve : public CCTransformAnimationCurve { +public: + // It is required that the keyframes be sorted by time. + static PassOwnPtr<CCKeyframedTransformAnimationCurve> create(); + + virtual ~CCKeyframedTransformAnimationCurve(); + + void addKeyframe(PassOwnPtr<CCTransformKeyframe>); + + // CCAnimationCurve implementation + virtual double duration() const OVERRIDE; + virtual PassOwnPtr<CCAnimationCurve> clone() const OVERRIDE; + + // CCTransformAnimationCurve implementation + virtual WebKit::WebTransformationMatrix getValue(double t) const OVERRIDE; + +private: + CCKeyframedTransformAnimationCurve(); + + // Always sorted in order of increasing time. No two keyframes have the + // same time. + Vector<OwnPtr<CCTransformKeyframe> > m_keyframes; +}; + +} // namespace WebCore + +#endif // CCKeyframedAnimationCurve_h diff --git a/cc/CCKeyframedAnimationCurveTest.cpp b/cc/CCKeyframedAnimationCurveTest.cpp new file mode 100644 index 0000000..8f8a746 --- /dev/null +++ b/cc/CCKeyframedAnimationCurveTest.cpp @@ -0,0 +1,208 @@ +// Copyright 2012 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 "CCKeyframedAnimationCurve.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/WebTransformOperations.h> +#include <public/WebTransformationMatrix.h> +#include <wtf/OwnPtr.h> +#include <wtf/Vector.h> + +using namespace WebCore; +using WebKit::WebTransformationMatrix; + +namespace { + +void expectTranslateX(double translateX, const WebTransformationMatrix& matrix) +{ + EXPECT_FLOAT_EQ(translateX, matrix.m41()); +} + +// Tests that a float animation with one keyframe works as expected. +TEST(CCKeyframedAnimationCurveTest, OneFloatKeyframe) +{ + OwnPtr<CCKeyframedFloatAnimationCurve> curve(CCKeyframedFloatAnimationCurve::create()); + curve->addKeyframe(CCFloatKeyframe::create(0, 2, nullptr)); + EXPECT_FLOAT_EQ(2, curve->getValue(-1)); + EXPECT_FLOAT_EQ(2, curve->getValue(0)); + EXPECT_FLOAT_EQ(2, curve->getValue(0.5)); + EXPECT_FLOAT_EQ(2, curve->getValue(1)); + EXPECT_FLOAT_EQ(2, curve->getValue(2)); +} + +// Tests that a float animation with two keyframes works as expected. +TEST(CCKeyframedAnimationCurveTest, TwoFloatKeyframe) +{ + OwnPtr<CCKeyframedFloatAnimationCurve> curve(CCKeyframedFloatAnimationCurve::create()); + curve->addKeyframe(CCFloatKeyframe::create(0, 2, nullptr)); + curve->addKeyframe(CCFloatKeyframe::create(1, 4, nullptr)); + EXPECT_FLOAT_EQ(2, curve->getValue(-1)); + EXPECT_FLOAT_EQ(2, curve->getValue(0)); + EXPECT_FLOAT_EQ(3, curve->getValue(0.5)); + EXPECT_FLOAT_EQ(4, curve->getValue(1)); + EXPECT_FLOAT_EQ(4, curve->getValue(2)); +} + +// Tests that a float animation with three keyframes works as expected. +TEST(CCKeyframedAnimationCurveTest, ThreeFloatKeyframe) +{ + OwnPtr<CCKeyframedFloatAnimationCurve> curve(CCKeyframedFloatAnimationCurve::create()); + curve->addKeyframe(CCFloatKeyframe::create(0, 2, nullptr)); + curve->addKeyframe(CCFloatKeyframe::create(1, 4, nullptr)); + curve->addKeyframe(CCFloatKeyframe::create(2, 8, nullptr)); + EXPECT_FLOAT_EQ(2, curve->getValue(-1)); + EXPECT_FLOAT_EQ(2, curve->getValue(0)); + EXPECT_FLOAT_EQ(3, curve->getValue(0.5)); + EXPECT_FLOAT_EQ(4, curve->getValue(1)); + EXPECT_FLOAT_EQ(6, curve->getValue(1.5)); + EXPECT_FLOAT_EQ(8, curve->getValue(2)); + EXPECT_FLOAT_EQ(8, curve->getValue(3)); +} + +// Tests that a float animation with multiple keys at a given time works sanely. +TEST(CCKeyframedAnimationCurveTest, RepeatedFloatKeyTimes) +{ + OwnPtr<CCKeyframedFloatAnimationCurve> curve(CCKeyframedFloatAnimationCurve::create()); + curve->addKeyframe(CCFloatKeyframe::create(0, 4, nullptr)); + curve->addKeyframe(CCFloatKeyframe::create(1, 4, nullptr)); + curve->addKeyframe(CCFloatKeyframe::create(1, 6, nullptr)); + curve->addKeyframe(CCFloatKeyframe::create(2, 6, nullptr)); + + EXPECT_FLOAT_EQ(4, curve->getValue(-1)); + EXPECT_FLOAT_EQ(4, curve->getValue(0)); + EXPECT_FLOAT_EQ(4, curve->getValue(0.5)); + + // There is a discontinuity at 1. Any value between 4 and 6 is valid. + float value = curve->getValue(1); + EXPECT_TRUE(value >= 4 && value <= 6); + + EXPECT_FLOAT_EQ(6, curve->getValue(1.5)); + EXPECT_FLOAT_EQ(6, curve->getValue(2)); + EXPECT_FLOAT_EQ(6, curve->getValue(3)); +} + + +// Tests that a transform animation with one keyframe works as expected. +TEST(CCKeyframedAnimationCurveTest, OneTransformKeyframe) +{ + OwnPtr<CCKeyframedTransformAnimationCurve> curve(CCKeyframedTransformAnimationCurve::create()); + WebKit::WebTransformOperations operations; + operations.appendTranslate(2, 0, 0); + curve->addKeyframe(CCTransformKeyframe::create(0, operations, nullptr)); + + expectTranslateX(2, curve->getValue(-1)); + expectTranslateX(2, curve->getValue(0)); + expectTranslateX(2, curve->getValue(0.5)); + expectTranslateX(2, curve->getValue(1)); + expectTranslateX(2, curve->getValue(2)); +} + +// Tests that a transform animation with two keyframes works as expected. +TEST(CCKeyframedAnimationCurveTest, TwoTransformKeyframe) +{ + OwnPtr<CCKeyframedTransformAnimationCurve> curve(CCKeyframedTransformAnimationCurve::create()); + WebKit::WebTransformOperations operations1; + operations1.appendTranslate(2, 0, 0); + WebKit::WebTransformOperations operations2; + operations2.appendTranslate(4, 0, 0); + + curve->addKeyframe(CCTransformKeyframe::create(0, operations1, nullptr)); + curve->addKeyframe(CCTransformKeyframe::create(1, operations2, nullptr)); + expectTranslateX(2, curve->getValue(-1)); + expectTranslateX(2, curve->getValue(0)); + expectTranslateX(3, curve->getValue(0.5)); + expectTranslateX(4, curve->getValue(1)); + expectTranslateX(4, curve->getValue(2)); +} + +// Tests that a transform animation with three keyframes works as expected. +TEST(CCKeyframedAnimationCurveTest, ThreeTransformKeyframe) +{ + OwnPtr<CCKeyframedTransformAnimationCurve> curve(CCKeyframedTransformAnimationCurve::create()); + WebKit::WebTransformOperations operations1; + operations1.appendTranslate(2, 0, 0); + WebKit::WebTransformOperations operations2; + operations2.appendTranslate(4, 0, 0); + WebKit::WebTransformOperations operations3; + operations3.appendTranslate(8, 0, 0); + curve->addKeyframe(CCTransformKeyframe::create(0, operations1, nullptr)); + curve->addKeyframe(CCTransformKeyframe::create(1, operations2, nullptr)); + curve->addKeyframe(CCTransformKeyframe::create(2, operations3, nullptr)); + expectTranslateX(2, curve->getValue(-1)); + expectTranslateX(2, curve->getValue(0)); + expectTranslateX(3, curve->getValue(0.5)); + expectTranslateX(4, curve->getValue(1)); + expectTranslateX(6, curve->getValue(1.5)); + expectTranslateX(8, curve->getValue(2)); + expectTranslateX(8, curve->getValue(3)); +} + +// Tests that a transform animation with multiple keys at a given time works sanely. +TEST(CCKeyframedAnimationCurveTest, RepeatedTransformKeyTimes) +{ + OwnPtr<CCKeyframedTransformAnimationCurve> curve(CCKeyframedTransformAnimationCurve::create()); + // A step function. + WebKit::WebTransformOperations operations1; + operations1.appendTranslate(4, 0, 0); + WebKit::WebTransformOperations operations2; + operations2.appendTranslate(4, 0, 0); + WebKit::WebTransformOperations operations3; + operations3.appendTranslate(6, 0, 0); + WebKit::WebTransformOperations operations4; + operations4.appendTranslate(6, 0, 0); + curve->addKeyframe(CCTransformKeyframe::create(0, operations1, nullptr)); + curve->addKeyframe(CCTransformKeyframe::create(1, operations2, nullptr)); + curve->addKeyframe(CCTransformKeyframe::create(1, operations3, nullptr)); + curve->addKeyframe(CCTransformKeyframe::create(2, operations4, nullptr)); + + expectTranslateX(4, curve->getValue(-1)); + expectTranslateX(4, curve->getValue(0)); + expectTranslateX(4, curve->getValue(0.5)); + + // There is a discontinuity at 1. Any value between 4 and 6 is valid. + WebTransformationMatrix value = curve->getValue(1); + EXPECT_TRUE(value.m41() >= 4 && value.m41() <= 6); + + expectTranslateX(6, curve->getValue(1.5)); + expectTranslateX(6, curve->getValue(2)); + expectTranslateX(6, curve->getValue(3)); +} + +// Tests that the keyframes may be added out of order. +TEST(CCKeyframedAnimationCurveTest, UnsortedKeyframes) +{ + OwnPtr<CCKeyframedFloatAnimationCurve> curve(CCKeyframedFloatAnimationCurve::create()); + curve->addKeyframe(CCFloatKeyframe::create(2, 8, nullptr)); + curve->addKeyframe(CCFloatKeyframe::create(0, 2, nullptr)); + curve->addKeyframe(CCFloatKeyframe::create(1, 4, nullptr)); + EXPECT_FLOAT_EQ(2, curve->getValue(-1)); + EXPECT_FLOAT_EQ(2, curve->getValue(0)); + EXPECT_FLOAT_EQ(3, curve->getValue(0.5)); + EXPECT_FLOAT_EQ(4, curve->getValue(1)); + EXPECT_FLOAT_EQ(6, curve->getValue(1.5)); + EXPECT_FLOAT_EQ(8, curve->getValue(2)); + EXPECT_FLOAT_EQ(8, curve->getValue(3)); +} + +// Tests that a cubic bezier timing function works as expected. +TEST(CCKeyframedAnimationCurveTest, CubicBezierTimingFunction) +{ + OwnPtr<CCKeyframedFloatAnimationCurve> curve(CCKeyframedFloatAnimationCurve::create()); + curve->addKeyframe(CCFloatKeyframe::create(0, 0, CCCubicBezierTimingFunction::create(0.25, 0, 0.75, 1))); + curve->addKeyframe(CCFloatKeyframe::create(1, 1, nullptr)); + + EXPECT_FLOAT_EQ(0, curve->getValue(0)); + EXPECT_LT(0, curve->getValue(0.25)); + EXPECT_GT(0.25, curve->getValue(0.25)); + EXPECT_FLOAT_EQ(0.5, curve->getValue(0.5)); + EXPECT_LT(0.75, curve->getValue(0.75)); + EXPECT_GT(1, curve->getValue(0.75)); + EXPECT_FLOAT_EQ(1, curve->getValue(1)); +} + +} // namespace diff --git a/cc/CCLayerAnimationController.cpp b/cc/CCLayerAnimationController.cpp new file mode 100644 index 0000000..bb60758 --- /dev/null +++ b/cc/CCLayerAnimationController.cpp @@ -0,0 +1,407 @@ +// Copyright 2012 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 "CCLayerAnimationController.h" + +#include "CCActiveAnimation.h" +#include "CCKeyframedAnimationCurve.h" +#include <public/WebTransformationMatrix.h> +#include <wtf/CurrentTime.h> +#include <wtf/HashMap.h> + +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +CCLayerAnimationController::CCLayerAnimationController(CCLayerAnimationControllerClient* client) + : m_forceSync(false) + , m_client(client) +{ +} + +CCLayerAnimationController::~CCLayerAnimationController() +{ +} + +PassOwnPtr<CCLayerAnimationController> CCLayerAnimationController::create(CCLayerAnimationControllerClient* client) +{ + return adoptPtr(new CCLayerAnimationController(client)); +} + +void CCLayerAnimationController::pauseAnimation(int animationId, double timeOffset) +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (m_activeAnimations[i]->id() == animationId) + m_activeAnimations[i]->setRunState(CCActiveAnimation::Paused, timeOffset + m_activeAnimations[i]->startTime()); + } +} + +void CCLayerAnimationController::removeAnimation(int animationId) +{ + for (size_t i = 0; i < m_activeAnimations.size();) { + if (m_activeAnimations[i]->id() == animationId) + m_activeAnimations.remove(i); + else + i++; + } +} + +void CCLayerAnimationController::removeAnimation(int animationId, CCActiveAnimation::TargetProperty targetProperty) +{ + for (size_t i = 0; i < m_activeAnimations.size();) { + if (m_activeAnimations[i]->id() == animationId && m_activeAnimations[i]->targetProperty() == targetProperty) + m_activeAnimations.remove(i); + else + i++; + } +} + +// According to render layer backing, these are for testing only. +void CCLayerAnimationController::suspendAnimations(double monotonicTime) +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (!m_activeAnimations[i]->isFinished()) + m_activeAnimations[i]->setRunState(CCActiveAnimation::Paused, monotonicTime); + } +} + +// Looking at GraphicsLayerCA, this appears to be the analog to suspendAnimations, which is for testing. +void CCLayerAnimationController::resumeAnimations(double monotonicTime) +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (m_activeAnimations[i]->runState() == CCActiveAnimation::Paused) + m_activeAnimations[i]->setRunState(CCActiveAnimation::Running, monotonicTime); + } +} + +// Ensures that the list of active animations on the main thread and the impl thread +// are kept in sync. +void CCLayerAnimationController::pushAnimationUpdatesTo(CCLayerAnimationController* controllerImpl) +{ + if (m_forceSync) { + replaceImplThreadAnimations(controllerImpl); + m_forceSync = false; + } else { + purgeAnimationsMarkedForDeletion(); + pushNewAnimationsToImplThread(controllerImpl); + + // Remove finished impl side animations only after pushing, + // and only after the animations are deleted on the main thread + // this insures we will never push an animation twice. + removeAnimationsCompletedOnMainThread(controllerImpl); + + pushPropertiesToImplThread(controllerImpl); + } +} + +void CCLayerAnimationController::animate(double monotonicTime, CCAnimationEventsVector* events) +{ + startAnimationsWaitingForNextTick(monotonicTime, events); + startAnimationsWaitingForStartTime(monotonicTime, events); + startAnimationsWaitingForTargetAvailability(monotonicTime, events); + resolveConflicts(monotonicTime); + tickAnimations(monotonicTime); + markAnimationsForDeletion(monotonicTime, events); + startAnimationsWaitingForTargetAvailability(monotonicTime, events); +} + +void CCLayerAnimationController::addAnimation(PassOwnPtr<CCActiveAnimation> animation) +{ + m_activeAnimations.append(animation); +} + +CCActiveAnimation* CCLayerAnimationController::getActiveAnimation(int groupId, CCActiveAnimation::TargetProperty targetProperty) const +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) + if (m_activeAnimations[i]->group() == groupId && m_activeAnimations[i]->targetProperty() == targetProperty) + return m_activeAnimations[i].get(); + return 0; +} + +CCActiveAnimation* CCLayerAnimationController::getActiveAnimation(CCActiveAnimation::TargetProperty targetProperty) const +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + size_t index = m_activeAnimations.size() - i - 1; + if (m_activeAnimations[index]->targetProperty() == targetProperty) + return m_activeAnimations[index].get(); + } + return 0; +} + +bool CCLayerAnimationController::hasActiveAnimation() const +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (!m_activeAnimations[i]->isFinished()) + return true; + } + return false; +} + +bool CCLayerAnimationController::isAnimatingProperty(CCActiveAnimation::TargetProperty targetProperty) const +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (m_activeAnimations[i]->runState() != CCActiveAnimation::Finished && m_activeAnimations[i]->runState() != CCActiveAnimation::Aborted && m_activeAnimations[i]->targetProperty() == targetProperty) + return true; + } + return false; +} + +void CCLayerAnimationController::notifyAnimationStarted(const CCAnimationEvent& event) +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (m_activeAnimations[i]->group() == event.groupId && m_activeAnimations[i]->targetProperty() == event.targetProperty && m_activeAnimations[i]->needsSynchronizedStartTime()) { + m_activeAnimations[i]->setNeedsSynchronizedStartTime(false); + m_activeAnimations[i]->setStartTime(event.monotonicTime); + return; + } + } +} + +void CCLayerAnimationController::setClient(CCLayerAnimationControllerClient* client) +{ + m_client = client; +} + +void CCLayerAnimationController::pushNewAnimationsToImplThread(CCLayerAnimationController* controllerImpl) const +{ + // Any new animations owned by the main thread's controller are cloned and adde to the impl thread's controller. + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + // If the animation is already running on the impl thread, there is no need to copy it over. + if (controllerImpl->getActiveAnimation(m_activeAnimations[i]->group(), m_activeAnimations[i]->targetProperty())) + continue; + + // If the animation is not running on the impl thread, it does not necessarily mean that it needs + // to be copied over and started; it may have already finished. In this case, the impl thread animation + // will have already notified that it has started and the main thread animation will no longer need + // a synchronized start time. + if (!m_activeAnimations[i]->needsSynchronizedStartTime()) + continue; + + // The new animation should be set to run as soon as possible. + CCActiveAnimation::RunState initialRunState = CCActiveAnimation::WaitingForTargetAvailability; + double startTime = 0; + OwnPtr<CCActiveAnimation> toAdd(m_activeAnimations[i]->cloneAndInitialize(CCActiveAnimation::ControllingInstance, initialRunState, startTime)); + ASSERT(!toAdd->needsSynchronizedStartTime()); + controllerImpl->addAnimation(toAdd.release()); + } +} + +void CCLayerAnimationController::removeAnimationsCompletedOnMainThread(CCLayerAnimationController* controllerImpl) const +{ + // Delete all impl thread animations for which there is no corresponding main thread animation. + // Each iteration, controller->m_activeAnimations.size() is decremented or i is incremented + // guaranteeing progress towards loop termination. + for (size_t i = 0; i < controllerImpl->m_activeAnimations.size();) { + CCActiveAnimation* current = getActiveAnimation(controllerImpl->m_activeAnimations[i]->group(), controllerImpl->m_activeAnimations[i]->targetProperty()); + if (!current) + controllerImpl->m_activeAnimations.remove(i); + else + i++; + } +} + +void CCLayerAnimationController::pushPropertiesToImplThread(CCLayerAnimationController* controllerImpl) const +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + CCActiveAnimation* currentImpl = controllerImpl->getActiveAnimation(m_activeAnimations[i]->group(), m_activeAnimations[i]->targetProperty()); + if (currentImpl) + m_activeAnimations[i]->pushPropertiesTo(currentImpl); + } +} + +void CCLayerAnimationController::startAnimationsWaitingForNextTick(double monotonicTime, CCAnimationEventsVector* events) +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (m_activeAnimations[i]->runState() == CCActiveAnimation::WaitingForNextTick) { + m_activeAnimations[i]->setRunState(CCActiveAnimation::Running, monotonicTime); + if (!m_activeAnimations[i]->hasSetStartTime()) + m_activeAnimations[i]->setStartTime(monotonicTime); + if (events) + events->append(CCAnimationEvent(CCAnimationEvent::Started, m_client->id(), m_activeAnimations[i]->group(), m_activeAnimations[i]->targetProperty(), monotonicTime)); + } + } +} + +void CCLayerAnimationController::startAnimationsWaitingForStartTime(double monotonicTime, CCAnimationEventsVector* events) +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (m_activeAnimations[i]->runState() == CCActiveAnimation::WaitingForStartTime && m_activeAnimations[i]->startTime() <= monotonicTime) { + m_activeAnimations[i]->setRunState(CCActiveAnimation::Running, monotonicTime); + if (events) + events->append(CCAnimationEvent(CCAnimationEvent::Started, m_client->id(), m_activeAnimations[i]->group(), m_activeAnimations[i]->targetProperty(), monotonicTime)); + } + } +} + +void CCLayerAnimationController::startAnimationsWaitingForTargetAvailability(double monotonicTime, CCAnimationEventsVector* events) +{ + // First collect running properties. + TargetProperties blockedProperties; + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (m_activeAnimations[i]->runState() == CCActiveAnimation::Running || m_activeAnimations[i]->runState() == CCActiveAnimation::Finished) + blockedProperties.add(m_activeAnimations[i]->targetProperty()); + } + + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (m_activeAnimations[i]->runState() == CCActiveAnimation::WaitingForTargetAvailability) { + // Collect all properties for animations with the same group id (they should all also be in the list of animations). + TargetProperties enqueuedProperties; + enqueuedProperties.add(m_activeAnimations[i]->targetProperty()); + for (size_t j = i + 1; j < m_activeAnimations.size(); ++j) { + if (m_activeAnimations[i]->group() == m_activeAnimations[j]->group()) + enqueuedProperties.add(m_activeAnimations[j]->targetProperty()); + } + + // Check to see if intersection of the list of properties affected by the group and the list of currently + // blocked properties is null. In any case, the group's target properties need to be added to the list + // of blocked properties. + bool nullIntersection = true; + for (TargetProperties::iterator pIter = enqueuedProperties.begin(); pIter != enqueuedProperties.end(); ++pIter) { + if (!blockedProperties.add(*pIter).isNewEntry) + nullIntersection = false; + } + + // If the intersection is null, then we are free to start the animations in the group. + if (nullIntersection) { + m_activeAnimations[i]->setRunState(CCActiveAnimation::Running, monotonicTime); + if (!m_activeAnimations[i]->hasSetStartTime()) + m_activeAnimations[i]->setStartTime(monotonicTime); + if (events) + events->append(CCAnimationEvent(CCAnimationEvent::Started, m_client->id(), m_activeAnimations[i]->group(), m_activeAnimations[i]->targetProperty(), monotonicTime)); + for (size_t j = i + 1; j < m_activeAnimations.size(); ++j) { + if (m_activeAnimations[i]->group() == m_activeAnimations[j]->group()) { + m_activeAnimations[j]->setRunState(CCActiveAnimation::Running, monotonicTime); + if (!m_activeAnimations[j]->hasSetStartTime()) + m_activeAnimations[j]->setStartTime(monotonicTime); + } + } + } + } + } +} + +void CCLayerAnimationController::resolveConflicts(double monotonicTime) +{ + // Find any animations that are animating the same property and resolve the + // confict. We could eventually blend, but for now we'll just abort the + // previous animation (where 'previous' means: (1) has a prior start time or + // (2) has an equal start time, but was added to the queue earlier, i.e., + // has a lower index in m_activeAnimations). + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (m_activeAnimations[i]->runState() == CCActiveAnimation::Running) { + for (size_t j = i + 1; j < m_activeAnimations.size(); ++j) { + if (m_activeAnimations[j]->runState() == CCActiveAnimation::Running && m_activeAnimations[i]->targetProperty() == m_activeAnimations[j]->targetProperty()) { + if (m_activeAnimations[i]->startTime() > m_activeAnimations[j]->startTime()) + m_activeAnimations[j]->setRunState(CCActiveAnimation::Aborted, monotonicTime); + else + m_activeAnimations[i]->setRunState(CCActiveAnimation::Aborted, monotonicTime); + } + } + } + } +} + +void CCLayerAnimationController::markAnimationsForDeletion(double monotonicTime, CCAnimationEventsVector* events) +{ + for (size_t i = 0; i < m_activeAnimations.size(); i++) { + int groupId = m_activeAnimations[i]->group(); + bool allAnimsWithSameIdAreFinished = false; + // If an animation is finished, and not already marked for deletion, + // Find out if all other animations in the same group are also finished. + if (m_activeAnimations[i]->isFinished()) { + allAnimsWithSameIdAreFinished = true; + for (size_t j = 0; j < m_activeAnimations.size(); ++j) { + if (groupId == m_activeAnimations[j]->group() && !m_activeAnimations[j]->isFinished()) { + allAnimsWithSameIdAreFinished = false; + break; + } + } + } + if (allAnimsWithSameIdAreFinished) { + // We now need to remove all animations with the same group id as groupId + // (and send along animation finished notifications, if necessary). + for (size_t j = i; j < m_activeAnimations.size(); j++) { + if (groupId == m_activeAnimations[j]->group()) { + if (events) + events->append(CCAnimationEvent(CCAnimationEvent::Finished, m_client->id(), m_activeAnimations[j]->group(), m_activeAnimations[j]->targetProperty(), monotonicTime)); + m_activeAnimations[j]->setRunState(CCActiveAnimation::WaitingForDeletion, monotonicTime); + } + } + } + } +} + +void CCLayerAnimationController::purgeAnimationsMarkedForDeletion() +{ + for (size_t i = 0; i < m_activeAnimations.size();) { + if (m_activeAnimations[i]->runState() == CCActiveAnimation::WaitingForDeletion) + m_activeAnimations.remove(i); + else + i++; + } +} + +void CCLayerAnimationController::replaceImplThreadAnimations(CCLayerAnimationController* controllerImpl) const +{ + controllerImpl->m_activeAnimations.clear(); + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + OwnPtr<CCActiveAnimation> toAdd; + if (m_activeAnimations[i]->needsSynchronizedStartTime()) { + // We haven't received an animation started notification yet, so it + // is important that we add it in a 'waiting' and not 'running' state. + CCActiveAnimation::RunState initialRunState = CCActiveAnimation::WaitingForTargetAvailability; + double startTime = 0; + toAdd = m_activeAnimations[i]->cloneAndInitialize(CCActiveAnimation::ControllingInstance, initialRunState, startTime); + } else + toAdd = m_activeAnimations[i]->clone(CCActiveAnimation::ControllingInstance); + + controllerImpl->addAnimation(toAdd.release()); + } +} + +void CCLayerAnimationController::tickAnimations(double monotonicTime) +{ + for (size_t i = 0; i < m_activeAnimations.size(); ++i) { + if (m_activeAnimations[i]->runState() == CCActiveAnimation::Running || m_activeAnimations[i]->runState() == CCActiveAnimation::Paused) { + double trimmed = m_activeAnimations[i]->trimTimeToCurrentIteration(monotonicTime); + + // Animation assumes its initial value until it gets the synchronized start time + // from the impl thread and can start ticking. + if (m_activeAnimations[i]->needsSynchronizedStartTime()) + trimmed = 0; + + switch (m_activeAnimations[i]->targetProperty()) { + + case CCActiveAnimation::Transform: { + const CCTransformAnimationCurve* transformAnimationCurve = m_activeAnimations[i]->curve()->toTransformAnimationCurve(); + const WebTransformationMatrix matrix = transformAnimationCurve->getValue(trimmed); + if (m_activeAnimations[i]->isFinishedAt(monotonicTime)) + m_activeAnimations[i]->setRunState(CCActiveAnimation::Finished, monotonicTime); + + m_client->setTransformFromAnimation(matrix); + break; + } + + case CCActiveAnimation::Opacity: { + const CCFloatAnimationCurve* floatAnimationCurve = m_activeAnimations[i]->curve()->toFloatAnimationCurve(); + const float opacity = floatAnimationCurve->getValue(trimmed); + if (m_activeAnimations[i]->isFinishedAt(monotonicTime)) + m_activeAnimations[i]->setRunState(CCActiveAnimation::Finished, monotonicTime); + + m_client->setOpacityFromAnimation(opacity); + break; + } + + // Do nothing for sentinel value. + case CCActiveAnimation::TargetPropertyEnumSize: + ASSERT_NOT_REACHED(); + + } + } + } +} + +} // namespace WebCore diff --git a/cc/CCLayerAnimationController.h b/cc/CCLayerAnimationController.h new file mode 100644 index 0000000..1176991 --- /dev/null +++ b/cc/CCLayerAnimationController.h @@ -0,0 +1,112 @@ +// Copyright 2012 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. + +#ifndef CCLayerAnimationController_h +#define CCLayerAnimationController_h + +#include "CCAnimationEvents.h" + +#include <wtf/HashSet.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/Vector.h> + +namespace WebKit { +class WebTransformationMatrix; +} + +namespace WebCore { + +class Animation; +class IntSize; +class KeyframeValueList; + +class CCLayerAnimationControllerClient { +public: + virtual ~CCLayerAnimationControllerClient() { } + + virtual int id() const = 0; + virtual void setOpacityFromAnimation(float) = 0; + virtual float opacity() const = 0; + virtual void setTransformFromAnimation(const WebKit::WebTransformationMatrix&) = 0; + virtual const WebKit::WebTransformationMatrix& transform() const = 0; +}; + +class CCLayerAnimationController { + WTF_MAKE_NONCOPYABLE(CCLayerAnimationController); +public: + static PassOwnPtr<CCLayerAnimationController> create(CCLayerAnimationControllerClient*); + + virtual ~CCLayerAnimationController(); + + // These methods are virtual for testing. + virtual void addAnimation(PassOwnPtr<CCActiveAnimation>); + virtual void pauseAnimation(int animationId, double timeOffset); + virtual void removeAnimation(int animationId); + virtual void removeAnimation(int animationId, CCActiveAnimation::TargetProperty); + virtual void suspendAnimations(double monotonicTime); + virtual void resumeAnimations(double monotonicTime); + + // Ensures that the list of active animations on the main thread and the impl thread + // are kept in sync. This function does not take ownership of the impl thread controller. + virtual void pushAnimationUpdatesTo(CCLayerAnimationController*); + + void animate(double monotonicTime, CCAnimationEventsVector*); + + // Returns the active animation in the given group, animating the given property, if such an + // animation exists. + CCActiveAnimation* getActiveAnimation(int groupId, CCActiveAnimation::TargetProperty) const; + + // Returns the active animation animating the given property that is either running, or is + // next to run, if such an animation exists. + CCActiveAnimation* getActiveAnimation(CCActiveAnimation::TargetProperty) const; + + // Returns true if there are any animations that have neither finished nor aborted. + bool hasActiveAnimation() const; + + // Returns true if there is an animation currently animating the given property, or + // if there is an animation scheduled to animate this property in the future. + bool isAnimatingProperty(CCActiveAnimation::TargetProperty) const; + + // This is called in response to an animation being started on the impl thread. This + // function updates the corresponding main thread animation's start time. + void notifyAnimationStarted(const CCAnimationEvent&); + + // If a sync is forced, then the next time animation updates are pushed to the impl + // thread, all animations will be transferred. + void setForceSync() { m_forceSync = true; } + + void setClient(CCLayerAnimationControllerClient*); + +protected: + explicit CCLayerAnimationController(CCLayerAnimationControllerClient*); + +private: + typedef HashSet<int, DefaultHash<int>::Hash, WTF::UnsignedWithZeroKeyHashTraits<int> > TargetProperties; + + void pushNewAnimationsToImplThread(CCLayerAnimationController*) const; + void removeAnimationsCompletedOnMainThread(CCLayerAnimationController*) const; + void pushPropertiesToImplThread(CCLayerAnimationController*) const; + void replaceImplThreadAnimations(CCLayerAnimationController*) const; + + void startAnimationsWaitingForNextTick(double monotonicTime, CCAnimationEventsVector*); + void startAnimationsWaitingForStartTime(double monotonicTime, CCAnimationEventsVector*); + void startAnimationsWaitingForTargetAvailability(double monotonicTime, CCAnimationEventsVector*); + void resolveConflicts(double monotonicTime); + void markAnimationsForDeletion(double monotonicTime, CCAnimationEventsVector*); + void purgeAnimationsMarkedForDeletion(); + + void tickAnimations(double monotonicTime); + + // If this is true, we force a sync to the impl thread. + bool m_forceSync; + + CCLayerAnimationControllerClient* m_client; + Vector<OwnPtr<CCActiveAnimation> > m_activeAnimations; +}; + +} // namespace WebCore + +#endif // CCLayerAnimationController_h diff --git a/cc/CCLayerAnimationControllerTest.cpp b/cc/CCLayerAnimationControllerTest.cpp new file mode 100644 index 0000000..9f03744 --- /dev/null +++ b/cc/CCLayerAnimationControllerTest.cpp @@ -0,0 +1,562 @@ +// Copyright 2012 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 "CCLayerAnimationController.h" + +#include "CCActiveAnimation.h" +#include "CCAnimationCurve.h" +#include "CCAnimationTestCommon.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/WebTransformationMatrix.h> +#include <wtf/Vector.h> + +using namespace WebCore; +using namespace WebKitTests; +using WebKit::WebTransformationMatrix; + +namespace { + +void expectTranslateX(double translateX, const WebTransformationMatrix& matrix) +{ + EXPECT_FLOAT_EQ(translateX, matrix.m41()); +} + +PassOwnPtr<CCActiveAnimation> createActiveAnimation(PassOwnPtr<CCAnimationCurve> curve, int id, CCActiveAnimation::TargetProperty property) +{ + return CCActiveAnimation::create(curve, 0, id, property); +} + +TEST(CCLayerAnimationControllerTest, syncNewAnimation) +{ + FakeLayerAnimationControllerClient dummyImpl; + OwnPtr<CCLayerAnimationController> controllerImpl(CCLayerAnimationController::create(&dummyImpl)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller(CCLayerAnimationController::create(&dummy)); + + EXPECT_FALSE(controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)); + + addOpacityTransitionToController(*controller, 1, 0, 1, false); + + controller->pushAnimationUpdatesTo(controllerImpl.get()); + + EXPECT_TRUE(controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)); + EXPECT_EQ(CCActiveAnimation::WaitingForTargetAvailability, controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)->runState()); +} + +// If an animation is started on the impl thread before it is ticked on the main +// thread, we must be sure to respect the synchronized start time. +TEST(CCLayerAnimationControllerTest, doNotClobberStartTimes) +{ + FakeLayerAnimationControllerClient dummyImpl; + OwnPtr<CCLayerAnimationController> controllerImpl(CCLayerAnimationController::create(&dummyImpl)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller(CCLayerAnimationController::create(&dummy)); + + EXPECT_FALSE(controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)); + + addOpacityTransitionToController(*controller, 1, 0, 1, false); + + controller->pushAnimationUpdatesTo(controllerImpl.get()); + + EXPECT_TRUE(controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)); + EXPECT_EQ(CCActiveAnimation::WaitingForTargetAvailability, controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)->runState()); + + CCAnimationEventsVector events; + controllerImpl->animate(1, &events); + + // Synchronize the start times. + EXPECT_EQ(1u, events.size()); + controller->notifyAnimationStarted(events[0]); + EXPECT_EQ(controller->getActiveAnimation(0, CCActiveAnimation::Opacity)->startTime(), controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)->startTime()); + + // Start the animation on the main thread. Should not affect the start time. + controller->animate(1.5, 0); + EXPECT_EQ(controller->getActiveAnimation(0, CCActiveAnimation::Opacity)->startTime(), controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)->startTime()); +} + +TEST(CCLayerAnimationControllerTest, syncPauseAndResume) +{ + FakeLayerAnimationControllerClient dummyImpl; + OwnPtr<CCLayerAnimationController> controllerImpl(CCLayerAnimationController::create(&dummyImpl)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller(CCLayerAnimationController::create(&dummy)); + + EXPECT_FALSE(controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)); + + addOpacityTransitionToController(*controller, 1, 0, 1, false); + + controller->pushAnimationUpdatesTo(controllerImpl.get()); + + EXPECT_TRUE(controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)); + EXPECT_EQ(CCActiveAnimation::WaitingForTargetAvailability, controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)->runState()); + + // Start the animations on each controller. + CCAnimationEventsVector events; + controllerImpl->animate(0, &events); + controller->animate(0, 0); + EXPECT_EQ(CCActiveAnimation::Running, controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)->runState()); + EXPECT_EQ(CCActiveAnimation::Running, controller->getActiveAnimation(0, CCActiveAnimation::Opacity)->runState()); + + // Pause the main-thread animation. + controller->suspendAnimations(1); + EXPECT_EQ(CCActiveAnimation::Paused, controller->getActiveAnimation(0, CCActiveAnimation::Opacity)->runState()); + + // The pause run state change should make it to the impl thread controller. + controller->pushAnimationUpdatesTo(controllerImpl.get()); + EXPECT_EQ(CCActiveAnimation::Paused, controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)->runState()); + + // Resume the main-thread animation. + controller->resumeAnimations(2); + EXPECT_EQ(CCActiveAnimation::Running, controller->getActiveAnimation(0, CCActiveAnimation::Opacity)->runState()); + + // The pause run state change should make it to the impl thread controller. + controller->pushAnimationUpdatesTo(controllerImpl.get()); + EXPECT_EQ(CCActiveAnimation::Running, controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)->runState()); +} + +TEST(CCLayerAnimationControllerTest, doNotSyncFinishedAnimation) +{ + FakeLayerAnimationControllerClient dummyImpl; + OwnPtr<CCLayerAnimationController> controllerImpl(CCLayerAnimationController::create(&dummyImpl)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller(CCLayerAnimationController::create(&dummy)); + + EXPECT_FALSE(controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)); + + addOpacityTransitionToController(*controller, 1, 0, 1, false); + + controller->pushAnimationUpdatesTo(controllerImpl.get()); + + EXPECT_TRUE(controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)); + EXPECT_EQ(CCActiveAnimation::WaitingForTargetAvailability, controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)->runState()); + + // Notify main thread controller that the animation has started. + CCAnimationEvent animationStartedEvent(CCAnimationEvent::Started, 0, 0, CCActiveAnimation::Opacity, 0); + controller->notifyAnimationStarted(animationStartedEvent); + + // Force animation to complete on impl thread. + controllerImpl->removeAnimation(0); + + EXPECT_FALSE(controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)); + + controller->pushAnimationUpdatesTo(controllerImpl.get()); + + // Even though the main thread has a 'new' animation, it should not be pushed because the animation has already completed on the impl thread. + EXPECT_FALSE(controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity)); +} + +// Tests that transitioning opacity from 0 to 1 works as expected. +TEST(CCLayerAnimationControllerTest, TrivialTransition) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + OwnPtr<CCActiveAnimation> toAdd(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 1)), 1, CCActiveAnimation::Opacity)); + + controller->addAnimation(toAdd.release()); + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(1, events.get()); + EXPECT_EQ(1, dummy.opacity()); + EXPECT_FALSE(controller->hasActiveAnimation()); +} + +// Tests animations that are waiting for a synchronized start time do not finish. +TEST(CCLayerAnimationControllerTest, AnimationsWaitingForStartTimeDoNotFinishIfTheyWaitLongerToStartThanTheirDuration) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + OwnPtr<CCActiveAnimation> toAdd(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 1)), 1, CCActiveAnimation::Opacity)); + toAdd->setNeedsSynchronizedStartTime(true); + + // We should pause at the first keyframe indefinitely waiting for that animation to start. + controller->addAnimation(toAdd.release()); + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(1, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(2, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + + // Send the synchronized start time. + controller->notifyAnimationStarted(CCAnimationEvent(CCAnimationEvent::Started, 0, 1, CCActiveAnimation::Opacity, 2)); + controller->animate(5, events.get()); + EXPECT_EQ(1, dummy.opacity()); + EXPECT_FALSE(controller->hasActiveAnimation()); +} + +// Tests that two queued animations affecting the same property run in sequence. +TEST(CCLayerAnimationControllerTest, TrivialQueuing) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 1)), 1, CCActiveAnimation::Opacity)); + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 1, 0.5)), 2, CCActiveAnimation::Opacity)); + + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(1, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(1, dummy.opacity()); + controller->animate(2, events.get()); + EXPECT_EQ(0.5, dummy.opacity()); + EXPECT_FALSE(controller->hasActiveAnimation()); +} + +// Tests interrupting a transition with another transition. +TEST(CCLayerAnimationControllerTest, Interrupt) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 1)), 1, CCActiveAnimation::Opacity)); + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + + OwnPtr<CCActiveAnimation> toAdd(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 1, 0.5)), 2, CCActiveAnimation::Opacity)); + toAdd->setRunState(CCActiveAnimation::WaitingForNextTick, 0); + controller->addAnimation(toAdd.release()); + + // Since the animation was in the WaitingForNextTick state, it should start right in + // this call to animate. + controller->animate(0.5, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(1, dummy.opacity()); + controller->animate(1.5, events.get()); + EXPECT_EQ(0.5, dummy.opacity()); + EXPECT_FALSE(controller->hasActiveAnimation()); +} + +// Tests scheduling two animations to run together when only one property is free. +TEST(CCLayerAnimationControllerTest, ScheduleTogetherWhenAPropertyIsBlocked) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeTransformTransition(1)), 1, CCActiveAnimation::Transform)); + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeTransformTransition(1)), 2, CCActiveAnimation::Transform)); + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 1)), 2, CCActiveAnimation::Opacity)); + + controller->animate(0, events.get()); + EXPECT_EQ(0, dummy.opacity()); + EXPECT_TRUE(controller->hasActiveAnimation()); + controller->animate(1, events.get()); + // Should not have started the float transition yet. + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + // The float animation should have started at time 1 and should be done. + controller->animate(2, events.get()); + EXPECT_EQ(1, dummy.opacity()); + EXPECT_FALSE(controller->hasActiveAnimation()); +} + +// Tests scheduling two animations to run together with different lengths and another +// animation queued to start when the shorter animation finishes (should wait +// for both to finish). +TEST(CCLayerAnimationControllerTest, ScheduleTogetherWithAnAnimWaiting) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeTransformTransition(2)), 1, CCActiveAnimation::Transform)); + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 1)), 1, CCActiveAnimation::Opacity)); + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 1, 0.5)), 2, CCActiveAnimation::Opacity)); + + // Animations with id 1 should both start now. + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + // The opacity animation should have finished at time 1, but the group + // of animations with id 1 don't finish until time 2 because of the length + // of the transform animation. + controller->animate(2, events.get()); + // Should not have started the float transition yet. + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(1, dummy.opacity()); + + // The second opacity animation should start at time 2 and should be done by time 3 + controller->animate(3, events.get()); + EXPECT_EQ(0.5, dummy.opacity()); + EXPECT_FALSE(controller->hasActiveAnimation()); +} + +// Tests scheduling an animation to start in the future. +TEST(CCLayerAnimationControllerTest, ScheduleAnimation) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + OwnPtr<CCActiveAnimation> toAdd(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 1)), 1, CCActiveAnimation::Opacity)); + toAdd->setRunState(CCActiveAnimation::WaitingForStartTime, 0); + toAdd->setStartTime(1); + controller->addAnimation(toAdd.release()); + + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(1, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(2, events.get()); + EXPECT_EQ(1, dummy.opacity()); + EXPECT_FALSE(controller->hasActiveAnimation()); +} + +// Tests scheduling an animation to start in the future that's interrupting a running animation. +TEST(CCLayerAnimationControllerTest, ScheduledAnimationInterruptsRunningAnimation) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(2, 0, 1)), 1, CCActiveAnimation::Opacity)); + + OwnPtr<CCActiveAnimation> toAdd(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0.5, 0)), 2, CCActiveAnimation::Opacity)); + toAdd->setRunState(CCActiveAnimation::WaitingForStartTime, 0); + toAdd->setStartTime(1); + controller->addAnimation(toAdd.release()); + + // First 2s opacity transition should start immediately. + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(0.5, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.25, dummy.opacity()); + controller->animate(1, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.5, dummy.opacity()); + controller->animate(2, events.get()); + EXPECT_EQ(0, dummy.opacity()); + EXPECT_FALSE(controller->hasActiveAnimation()); +} + +// Tests scheduling an animation to start in the future that interrupts a running animation +// and there is yet another animation queued to start later. +TEST(CCLayerAnimationControllerTest, ScheduledAnimationInterruptsRunningAnimationWithAnimInQueue) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(2, 0, 1)), 1, CCActiveAnimation::Opacity)); + + OwnPtr<CCActiveAnimation> toAdd(createActiveAnimation(adoptPtr(new FakeFloatTransition(2, 0.5, 0)), 2, CCActiveAnimation::Opacity)); + toAdd->setRunState(CCActiveAnimation::WaitingForStartTime, 0); + toAdd->setStartTime(1); + controller->addAnimation(toAdd.release()); + + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 0.75)), 3, CCActiveAnimation::Opacity)); + + // First 2s opacity transition should start immediately. + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(0.5, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.25, dummy.opacity()); + EXPECT_TRUE(controller->hasActiveAnimation()); + controller->animate(1, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.5, dummy.opacity()); + controller->animate(3, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(4, events.get()); + EXPECT_EQ(0.75, dummy.opacity()); + EXPECT_FALSE(controller->hasActiveAnimation()); +} + +// Test that a looping animation loops and for the correct number of iterations. +TEST(CCLayerAnimationControllerTest, TrivialLooping) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + OwnPtr<CCActiveAnimation> toAdd(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 1)), 1, CCActiveAnimation::Opacity)); + toAdd->setIterations(3); + controller->addAnimation(toAdd.release()); + + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(1.25, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.25, dummy.opacity()); + controller->animate(1.75, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.75, dummy.opacity()); + controller->animate(2.25, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.25, dummy.opacity()); + controller->animate(2.75, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.75, dummy.opacity()); + controller->animate(3, events.get()); + EXPECT_FALSE(controller->hasActiveAnimation()); + EXPECT_EQ(1, dummy.opacity()); + + // Just be extra sure. + controller->animate(4, events.get()); + EXPECT_EQ(1, dummy.opacity()); +} + +// Test that an infinitely looping animation does indeed go until aborted. +TEST(CCLayerAnimationControllerTest, InfiniteLooping) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + const int id = 1; + OwnPtr<CCActiveAnimation> toAdd(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 1)), id, CCActiveAnimation::Opacity)); + toAdd->setIterations(-1); + controller->addAnimation(toAdd.release()); + + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(1.25, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.25, dummy.opacity()); + controller->animate(1.75, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.75, dummy.opacity()); + + controller->animate(1073741824.25, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.25, dummy.opacity()); + controller->animate(1073741824.75, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.75, dummy.opacity()); + + EXPECT_TRUE(controller->getActiveAnimation(id, CCActiveAnimation::Opacity)); + controller->getActiveAnimation(id, CCActiveAnimation::Opacity)->setRunState(CCActiveAnimation::Aborted, 0.75); + EXPECT_FALSE(controller->hasActiveAnimation()); + EXPECT_EQ(0.75, dummy.opacity()); +} + +// Test that pausing and resuming work as expected. +TEST(CCLayerAnimationControllerTest, PauseResume) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + const int id = 1; + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 0, 1)), id, CCActiveAnimation::Opacity)); + + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(0.5, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.5, dummy.opacity()); + + EXPECT_TRUE(controller->getActiveAnimation(id, CCActiveAnimation::Opacity)); + controller->getActiveAnimation(id, CCActiveAnimation::Opacity)->setRunState(CCActiveAnimation::Paused, 0.5); + + controller->animate(1024, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.5, dummy.opacity()); + + EXPECT_TRUE(controller->getActiveAnimation(id, CCActiveAnimation::Opacity)); + controller->getActiveAnimation(id, CCActiveAnimation::Opacity)->setRunState(CCActiveAnimation::Running, 1024); + + controller->animate(1024.25, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.75, dummy.opacity()); + controller->animate(1024.5, events.get()); + EXPECT_FALSE(controller->hasActiveAnimation()); + EXPECT_EQ(1, dummy.opacity()); +} + +TEST(CCLayerAnimationControllerTest, AbortAGroupedAnimation) +{ + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + const int id = 1; + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeTransformTransition(1)), id, CCActiveAnimation::Transform)); + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(2, 0, 1)), id, CCActiveAnimation::Opacity)); + controller->addAnimation(createActiveAnimation(adoptPtr(new FakeFloatTransition(1, 1, 0.75)), 2, CCActiveAnimation::Opacity)); + + controller->animate(0, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0, dummy.opacity()); + controller->animate(1, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(0.5, dummy.opacity()); + + EXPECT_TRUE(controller->getActiveAnimation(id, CCActiveAnimation::Opacity)); + controller->getActiveAnimation(id, CCActiveAnimation::Opacity)->setRunState(CCActiveAnimation::Aborted, 1); + controller->animate(1, events.get()); + EXPECT_TRUE(controller->hasActiveAnimation()); + EXPECT_EQ(1, dummy.opacity()); + controller->animate(2, events.get()); + EXPECT_TRUE(!controller->hasActiveAnimation()); + EXPECT_EQ(0.75, dummy.opacity()); +} + +TEST(CCLayerAnimationControllerTest, ForceSyncWhenSynchronizedStartTimeNeeded) +{ + FakeLayerAnimationControllerClient dummyImpl; + OwnPtr<CCLayerAnimationController> controllerImpl(CCLayerAnimationController::create(&dummyImpl)); + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + FakeLayerAnimationControllerClient dummy; + OwnPtr<CCLayerAnimationController> controller( + CCLayerAnimationController::create(&dummy)); + + OwnPtr<CCActiveAnimation> toAdd(createActiveAnimation(adoptPtr(new FakeFloatTransition(2, 0, 1)), 0, CCActiveAnimation::Opacity)); + toAdd->setNeedsSynchronizedStartTime(true); + controller->addAnimation(toAdd.release()); + + controller->animate(0, 0); + EXPECT_TRUE(controller->hasActiveAnimation()); + CCActiveAnimation* activeAnimation = controller->getActiveAnimation(0, CCActiveAnimation::Opacity); + EXPECT_TRUE(activeAnimation); + EXPECT_TRUE(activeAnimation->needsSynchronizedStartTime()); + + controller->setForceSync(); + + controller->pushAnimationUpdatesTo(controllerImpl.get()); + + activeAnimation = controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity); + EXPECT_TRUE(activeAnimation); + EXPECT_EQ(CCActiveAnimation::WaitingForTargetAvailability, activeAnimation->runState()); +} + +} // namespace diff --git a/cc/CCLayerImpl.cpp b/cc/CCLayerImpl.cpp new file mode 100644 index 0000000..0a5df9e --- /dev/null +++ b/cc/CCLayerImpl.cpp @@ -0,0 +1,626 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCLayerImpl.h" + +#include "CCDebugBorderDrawQuad.h" +#include "CCLayerSorter.h" +#include "CCMathUtil.h" +#include "CCProxy.h" +#include "CCQuadSink.h" +#include "CCScrollbarAnimationController.h" +#include "TextStream.h" +#include "TraceEvent.h" +#include <wtf/text/WTFString.h> + +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +CCLayerImpl::CCLayerImpl(int id) + : m_parent(0) + , m_maskLayerId(-1) + , m_replicaLayerId(-1) + , m_layerId(id) + , m_layerTreeHostImpl(0) + , m_anchorPoint(0.5, 0.5) + , m_anchorPointZ(0) + , m_scrollable(false) + , m_shouldScrollOnMainThread(false) + , m_haveWheelEventHandlers(false) + , m_backgroundColor(0) + , m_doubleSided(true) + , m_layerPropertyChanged(false) + , m_layerSurfacePropertyChanged(false) + , m_masksToBounds(false) + , m_opaque(false) + , m_opacity(1.0) + , m_preserves3D(false) + , m_useParentBackfaceVisibility(false) + , m_drawCheckerboardForMissingTiles(false) + , m_useLCDText(false) + , m_drawsContent(false) + , m_forceRenderSurface(false) + , m_isContainerForFixedPositionLayers(false) + , m_fixedToContainerLayer(false) + , m_pageScaleDelta(1) + , m_renderTarget(0) + , m_drawDepth(0) + , m_drawOpacity(0) + , m_drawOpacityIsAnimating(false) + , m_debugBorderColor(0) + , m_debugBorderWidth(0) + , m_drawTransformIsAnimating(false) + , m_screenSpaceTransformIsAnimating(false) +#ifndef NDEBUG + , m_betweenWillDrawAndDidDraw(false) +#endif + , m_layerAnimationController(CCLayerAnimationController::create(this)) +{ + ASSERT(CCProxy::isImplThread()); + ASSERT(m_layerId > 0); +} + +CCLayerImpl::~CCLayerImpl() +{ + ASSERT(CCProxy::isImplThread()); +#ifndef NDEBUG + ASSERT(!m_betweenWillDrawAndDidDraw); +#endif +} + +void CCLayerImpl::addChild(PassOwnPtr<CCLayerImpl> child) +{ + child->setParent(this); + m_children.append(child); +} + +void CCLayerImpl::removeFromParent() +{ + if (!m_parent) + return; + + CCLayerImpl* parent = m_parent; + m_parent = 0; + + for (size_t i = 0; i < parent->m_children.size(); ++i) { + if (parent->m_children[i].get() == this) { + parent->m_children.remove(i); + return; + } + } +} + +void CCLayerImpl::removeAllChildren() +{ + while (m_children.size()) + m_children[0]->removeFromParent(); +} + +void CCLayerImpl::clearChildList() +{ + m_children.clear(); +} + +void CCLayerImpl::createRenderSurface() +{ + ASSERT(!m_renderSurface); + m_renderSurface = adoptPtr(new CCRenderSurface(this)); + setRenderTarget(this); +} + +bool CCLayerImpl::descendantDrawsContent() +{ + for (size_t i = 0; i < m_children.size(); ++i) { + if (m_children[i]->drawsContent() || m_children[i]->descendantDrawsContent()) + return true; + } + return false; +} + +PassOwnPtr<CCSharedQuadState> CCLayerImpl::createSharedQuadState() const +{ + return CCSharedQuadState::create(m_drawTransform, m_visibleContentRect, m_drawableContentRect, m_drawOpacity, m_opaque); +} + +void CCLayerImpl::willDraw(CCResourceProvider*) +{ +#ifndef NDEBUG + // willDraw/didDraw must be matched. + ASSERT(!m_betweenWillDrawAndDidDraw); + m_betweenWillDrawAndDidDraw = true; +#endif +} + +void CCLayerImpl::didDraw(CCResourceProvider*) +{ +#ifndef NDEBUG + ASSERT(m_betweenWillDrawAndDidDraw); + m_betweenWillDrawAndDidDraw = false; +#endif +} + +void CCLayerImpl::appendDebugBorderQuad(CCQuadSink& quadList, const CCSharedQuadState* sharedQuadState) const +{ + if (!hasDebugBorders()) + return; + + IntRect contentRect(IntPoint(), contentBounds()); + quadList.append(CCDebugBorderDrawQuad::create(sharedQuadState, contentRect, debugBorderColor(), debugBorderWidth())); +} + +CCResourceProvider::ResourceId CCLayerImpl::contentsResourceId() const +{ + ASSERT_NOT_REACHED(); + return 0; +} + +void CCLayerImpl::scrollBy(const FloatSize& scroll) +{ + IntSize minDelta = -toSize(m_scrollPosition); + IntSize maxDelta = m_maxScrollPosition - toSize(m_scrollPosition); + // Clamp newDelta so that position + delta stays within scroll bounds. + FloatSize newDelta = (m_scrollDelta + scroll).expandedTo(minDelta).shrunkTo(maxDelta); + + if (m_scrollDelta == newDelta) + return; + + m_scrollDelta = newDelta; + if (m_scrollbarAnimationController) + m_scrollbarAnimationController->updateScrollOffset(this); + noteLayerPropertyChangedForSubtree(); +} + +CCInputHandlerClient::ScrollStatus CCLayerImpl::tryScroll(const IntPoint& viewportPoint, CCInputHandlerClient::ScrollInputType type) const +{ + if (shouldScrollOnMainThread()) { + TRACE_EVENT0("cc", "CCLayerImpl::tryScroll: Failed shouldScrollOnMainThread"); + return CCInputHandlerClient::ScrollOnMainThread; + } + + if (!screenSpaceTransform().isInvertible()) { + TRACE_EVENT0("cc", "CCLayerImpl::tryScroll: Ignored nonInvertibleTransform"); + return CCInputHandlerClient::ScrollIgnored; + } + + if (!nonFastScrollableRegion().isEmpty()) { + bool clipped = false; + FloatPoint hitTestPointInLocalSpace = CCMathUtil::projectPoint(screenSpaceTransform().inverse(), FloatPoint(viewportPoint), clipped); + if (!clipped && nonFastScrollableRegion().contains(flooredIntPoint(hitTestPointInLocalSpace))) { + TRACE_EVENT0("cc", "CCLayerImpl::tryScroll: Failed nonFastScrollableRegion"); + return CCInputHandlerClient::ScrollOnMainThread; + } + } + + if (type == CCInputHandlerClient::Wheel && haveWheelEventHandlers()) { + TRACE_EVENT0("cc", "CCLayerImpl::tryScroll: Failed wheelEventHandlers"); + return CCInputHandlerClient::ScrollOnMainThread; + } + + if (!scrollable()) { + TRACE_EVENT0("cc", "CCLayerImpl::tryScroll: Ignored not scrollable"); + return CCInputHandlerClient::ScrollIgnored; + } + + return CCInputHandlerClient::ScrollStarted; +} + +void CCLayerImpl::writeIndent(TextStream& ts, int indent) +{ + for (int i = 0; i != indent; ++i) + ts << " "; +} + +void CCLayerImpl::dumpLayerProperties(TextStream& ts, int indent) const +{ + writeIndent(ts, indent); + ts << "layer ID: " << m_layerId << "\n"; + + writeIndent(ts, indent); + ts << "bounds: " << bounds().width() << ", " << bounds().height() << "\n"; + + if (m_renderTarget) { + writeIndent(ts, indent); + ts << "renderTarget: " << m_renderTarget->m_layerId << "\n"; + } + + writeIndent(ts, indent); + ts << "drawTransform: "; + ts << m_drawTransform.m11() << ", " << m_drawTransform.m12() << ", " << m_drawTransform.m13() << ", " << m_drawTransform.m14() << " // "; + ts << m_drawTransform.m21() << ", " << m_drawTransform.m22() << ", " << m_drawTransform.m23() << ", " << m_drawTransform.m24() << " // "; + ts << m_drawTransform.m31() << ", " << m_drawTransform.m32() << ", " << m_drawTransform.m33() << ", " << m_drawTransform.m34() << " // "; + ts << m_drawTransform.m41() << ", " << m_drawTransform.m42() << ", " << m_drawTransform.m43() << ", " << m_drawTransform.m44() << "\n"; + + writeIndent(ts, indent); + ts << "drawsContent: " << (m_drawsContent ? "yes" : "no") << "\n"; +} + +void sortLayers(Vector<CCLayerImpl*>::iterator first, Vector<CCLayerImpl*>::iterator end, CCLayerSorter* layerSorter) +{ + TRACE_EVENT0("cc", "CCLayerImpl::sortLayers"); + layerSorter->sort(first, end); +} + +String CCLayerImpl::layerTreeAsText() const +{ + TextStream ts; + dumpLayer(ts, 0); + return ts.release(); +} + +void CCLayerImpl::dumpLayer(TextStream& ts, int indent) const +{ + writeIndent(ts, indent); + ts << layerTypeAsString() << "(" << m_debugName << ")\n"; + dumpLayerProperties(ts, indent+2); + if (m_replicaLayer) { + writeIndent(ts, indent+2); + ts << "Replica:\n"; + m_replicaLayer->dumpLayer(ts, indent+3); + } + if (m_maskLayer) { + writeIndent(ts, indent+2); + ts << "Mask:\n"; + m_maskLayer->dumpLayer(ts, indent+3); + } + for (size_t i = 0; i < m_children.size(); ++i) + m_children[i]->dumpLayer(ts, indent+1); +} + +void CCLayerImpl::setStackingOrderChanged(bool stackingOrderChanged) +{ + // We don't need to store this flag; we only need to track that the change occurred. + if (stackingOrderChanged) + noteLayerPropertyChangedForSubtree(); +} + +bool CCLayerImpl::layerSurfacePropertyChanged() const +{ + if (m_layerSurfacePropertyChanged) + return true; + + // If this layer's surface property hasn't changed, we want to see if + // some layer above us has changed this property. This is done for the + // case when such parent layer does not draw content, and therefore will + // not be traversed by the damage tracker. We need to make sure that + // property change on such layer will be caught by its descendants. + CCLayerImpl* current = this->m_parent; + while (current && !current->m_renderSurface) { + if (current->m_layerSurfacePropertyChanged) + return true; + current = current->m_parent; + } + + return false; +} + +void CCLayerImpl::noteLayerPropertyChangedForSubtree() +{ + m_layerPropertyChanged = true; + noteLayerPropertyChangedForDescendants(); +} + +void CCLayerImpl::noteLayerPropertyChangedForDescendants() +{ + for (size_t i = 0; i < m_children.size(); ++i) + m_children[i]->noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::resetAllChangeTrackingForSubtree() +{ + m_layerPropertyChanged = false; + m_layerSurfacePropertyChanged = false; + + m_updateRect = FloatRect(); + + if (m_renderSurface) + m_renderSurface->resetPropertyChangedFlag(); + + if (m_maskLayer) + m_maskLayer->resetAllChangeTrackingForSubtree(); + + if (m_replicaLayer) + m_replicaLayer->resetAllChangeTrackingForSubtree(); // also resets the replica mask, if it exists. + + for (size_t i = 0; i < m_children.size(); ++i) + m_children[i]->resetAllChangeTrackingForSubtree(); +} + +void CCLayerImpl::setOpacityFromAnimation(float opacity) +{ + setOpacity(opacity); +} + +void CCLayerImpl::setTransformFromAnimation(const WebTransformationMatrix& transform) +{ + setTransform(transform); +} + +void CCLayerImpl::setBounds(const IntSize& bounds) +{ + if (m_bounds == bounds) + return; + + m_bounds = bounds; + + if (masksToBounds()) + noteLayerPropertyChangedForSubtree(); + else + m_layerPropertyChanged = true; +} + +void CCLayerImpl::setMaskLayer(PassOwnPtr<CCLayerImpl> maskLayer) +{ + m_maskLayer = maskLayer; + + int newLayerId = m_maskLayer ? m_maskLayer->id() : -1; + if (newLayerId == m_maskLayerId) + return; + + m_maskLayerId = newLayerId; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setReplicaLayer(PassOwnPtr<CCLayerImpl> replicaLayer) +{ + m_replicaLayer = replicaLayer; + + int newLayerId = m_replicaLayer ? m_replicaLayer->id() : -1; + if (newLayerId == m_replicaLayerId) + return; + + m_replicaLayerId = newLayerId; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setDrawsContent(bool drawsContent) +{ + if (m_drawsContent == drawsContent) + return; + + m_drawsContent = drawsContent; + m_layerPropertyChanged = true; +} + +void CCLayerImpl::setAnchorPoint(const FloatPoint& anchorPoint) +{ + if (m_anchorPoint == anchorPoint) + return; + + m_anchorPoint = anchorPoint; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setAnchorPointZ(float anchorPointZ) +{ + if (m_anchorPointZ == anchorPointZ) + return; + + m_anchorPointZ = anchorPointZ; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setBackgroundColor(SkColor backgroundColor) +{ + if (m_backgroundColor == backgroundColor) + return; + + m_backgroundColor = backgroundColor; + m_layerPropertyChanged = true; +} + +void CCLayerImpl::setFilters(const WebKit::WebFilterOperations& filters) +{ + if (m_filters == filters) + return; + + m_filters = filters; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setBackgroundFilters(const WebKit::WebFilterOperations& backgroundFilters) +{ + if (m_backgroundFilters == backgroundFilters) + return; + + m_backgroundFilters = backgroundFilters; + m_layerPropertyChanged = true; +} + +void CCLayerImpl::setMasksToBounds(bool masksToBounds) +{ + if (m_masksToBounds == masksToBounds) + return; + + m_masksToBounds = masksToBounds; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setOpaque(bool opaque) +{ + if (m_opaque == opaque) + return; + + m_opaque = opaque; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setOpacity(float opacity) +{ + if (m_opacity == opacity) + return; + + m_opacity = opacity; + m_layerSurfacePropertyChanged = true; +} + +bool CCLayerImpl::opacityIsAnimating() const +{ + return m_layerAnimationController->isAnimatingProperty(CCActiveAnimation::Opacity); +} + +void CCLayerImpl::setPosition(const FloatPoint& position) +{ + if (m_position == position) + return; + + m_position = position; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setPreserves3D(bool preserves3D) +{ + if (m_preserves3D == preserves3D) + return; + + m_preserves3D = preserves3D; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setSublayerTransform(const WebTransformationMatrix& sublayerTransform) +{ + if (m_sublayerTransform == sublayerTransform) + return; + + m_sublayerTransform = sublayerTransform; + // sublayer transform does not affect the current layer; it affects only its children. + noteLayerPropertyChangedForDescendants(); +} + +void CCLayerImpl::setTransform(const WebTransformationMatrix& transform) +{ + if (m_transform == transform) + return; + + m_transform = transform; + m_layerSurfacePropertyChanged = true; +} + +bool CCLayerImpl::transformIsAnimating() const +{ + return m_layerAnimationController->isAnimatingProperty(CCActiveAnimation::Transform); +} + +void CCLayerImpl::setDebugBorderColor(SkColor debugBorderColor) +{ + if (m_debugBorderColor == debugBorderColor) + return; + + m_debugBorderColor = debugBorderColor; + m_layerPropertyChanged = true; +} + +void CCLayerImpl::setDebugBorderWidth(float debugBorderWidth) +{ + if (m_debugBorderWidth == debugBorderWidth) + return; + + m_debugBorderWidth = debugBorderWidth; + m_layerPropertyChanged = true; +} + +bool CCLayerImpl::hasDebugBorders() const +{ + return SkColorGetA(m_debugBorderColor) && debugBorderWidth() > 0; +} + +void CCLayerImpl::setContentBounds(const IntSize& contentBounds) +{ + if (m_contentBounds == contentBounds) + return; + + m_contentBounds = contentBounds; + m_layerPropertyChanged = true; +} + +void CCLayerImpl::setScrollPosition(const IntPoint& scrollPosition) +{ + if (m_scrollPosition == scrollPosition) + return; + + m_scrollPosition = scrollPosition; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setScrollDelta(const FloatSize& scrollDelta) +{ + if (m_scrollDelta == scrollDelta) + return; + + m_scrollDelta = scrollDelta; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setPageScaleDelta(float pageScaleDelta) +{ + if (m_pageScaleDelta == pageScaleDelta) + return; + + m_pageScaleDelta = pageScaleDelta; + noteLayerPropertyChangedForSubtree(); +} + +void CCLayerImpl::setDoubleSided(bool doubleSided) +{ + if (m_doubleSided == doubleSided) + return; + + m_doubleSided = doubleSided; + noteLayerPropertyChangedForSubtree(); +} + +Region CCLayerImpl::visibleContentOpaqueRegion() const +{ + if (opaque()) + return visibleContentRect(); + return Region(); +} + +void CCLayerImpl::didLoseContext() +{ +} + +void CCLayerImpl::setMaxScrollPosition(const IntSize& maxScrollPosition) +{ + m_maxScrollPosition = maxScrollPosition; + + if (!m_scrollbarAnimationController) + return; + m_scrollbarAnimationController->updateScrollOffset(this); +} + +CCScrollbarLayerImpl* CCLayerImpl::horizontalScrollbarLayer() const +{ + return m_scrollbarAnimationController ? m_scrollbarAnimationController->horizontalScrollbarLayer() : 0; +} + +void CCLayerImpl::setHorizontalScrollbarLayer(CCScrollbarLayerImpl* scrollbarLayer) +{ + if (!m_scrollbarAnimationController) + m_scrollbarAnimationController = CCScrollbarAnimationController::create(this); + m_scrollbarAnimationController->setHorizontalScrollbarLayer(scrollbarLayer); + m_scrollbarAnimationController->updateScrollOffset(this); +} + +CCScrollbarLayerImpl* CCLayerImpl::verticalScrollbarLayer() const +{ + return m_scrollbarAnimationController ? m_scrollbarAnimationController->verticalScrollbarLayer() : 0; +} + +void CCLayerImpl::setVerticalScrollbarLayer(CCScrollbarLayerImpl* scrollbarLayer) +{ + if (!m_scrollbarAnimationController) + m_scrollbarAnimationController = CCScrollbarAnimationController::create(this); + m_scrollbarAnimationController->setVerticalScrollbarLayer(scrollbarLayer); + m_scrollbarAnimationController->updateScrollOffset(this); +} + +} + + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCLayerImpl.h b/cc/CCLayerImpl.h new file mode 100644 index 0000000..3e42478 --- /dev/null +++ b/cc/CCLayerImpl.h @@ -0,0 +1,390 @@ +// 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. + +#ifndef CCLayerImpl_h +#define CCLayerImpl_h + +#include "CCInputHandler.h" +#include "CCLayerAnimationController.h" +#include "CCRenderSurface.h" +#include "CCResourceProvider.h" +#include "CCSharedQuadState.h" +#include "FloatRect.h" +#include "IntRect.h" +#include "Region.h" +#include "SkColor.h" +#include "TextStream.h" +#include <public/WebFilterOperations.h> +#include <public/WebTransformationMatrix.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class CCLayerSorter; +class CCLayerTreeHostImpl; +class CCRenderer; +class CCQuadSink; +class CCScrollbarAnimationController; +class CCScrollbarLayerImpl; +class LayerChromium; + +class CCLayerImpl : public CCLayerAnimationControllerClient { +public: + static PassOwnPtr<CCLayerImpl> create(int id) + { + return adoptPtr(new CCLayerImpl(id)); + } + + virtual ~CCLayerImpl(); + + // CCLayerAnimationControllerClient implementation. + virtual int id() const OVERRIDE { return m_layerId; } + virtual void setOpacityFromAnimation(float) OVERRIDE; + virtual float opacity() const OVERRIDE { return m_opacity; } + virtual void setTransformFromAnimation(const WebKit::WebTransformationMatrix&) OVERRIDE; + virtual const WebKit::WebTransformationMatrix& transform() const OVERRIDE { return m_transform; } + + // Tree structure. + CCLayerImpl* parent() const { return m_parent; } + const Vector<OwnPtr<CCLayerImpl> >& children() const { return m_children; } + void addChild(PassOwnPtr<CCLayerImpl>); + void removeFromParent(); + void removeAllChildren(); + + void setMaskLayer(PassOwnPtr<CCLayerImpl>); + CCLayerImpl* maskLayer() const { return m_maskLayer.get(); } + + void setReplicaLayer(PassOwnPtr<CCLayerImpl>); + CCLayerImpl* replicaLayer() const { return m_replicaLayer.get(); } + + bool hasMask() const { return m_maskLayer; } + bool hasReplica() const { return m_replicaLayer; } + bool replicaHasMask() const { return m_replicaLayer && (m_maskLayer || m_replicaLayer->m_maskLayer); } + + CCLayerTreeHostImpl* layerTreeHostImpl() const { return m_layerTreeHostImpl; } + void setLayerTreeHostImpl(CCLayerTreeHostImpl* hostImpl) { m_layerTreeHostImpl = hostImpl; } + + PassOwnPtr<CCSharedQuadState> createSharedQuadState() const; + // willDraw must be called before appendQuads. If willDraw is called, + // didDraw is guaranteed to be called before another willDraw or before + // the layer is destroyed. To enforce this, any class that overrides + // willDraw/didDraw must call the base class version. + virtual void willDraw(CCResourceProvider*); + virtual void appendQuads(CCQuadSink&, bool& hadMissingTiles) { } + virtual void didDraw(CCResourceProvider*); + + virtual CCResourceProvider::ResourceId contentsResourceId() const; + + // Returns true if this layer has content to draw. + void setDrawsContent(bool); + bool drawsContent() const { return m_drawsContent; } + + bool forceRenderSurface() const { return m_forceRenderSurface; } + void setForceRenderSurface(bool force) { m_forceRenderSurface = force; } + + // Returns true if any of the layer's descendants has content to draw. + bool descendantDrawsContent(); + + void setAnchorPoint(const FloatPoint&); + const FloatPoint& anchorPoint() const { return m_anchorPoint; } + + void setAnchorPointZ(float); + float anchorPointZ() const { return m_anchorPointZ; } + + void setBackgroundColor(SkColor); + SkColor backgroundColor() const { return m_backgroundColor; } + + void setFilters(const WebKit::WebFilterOperations&); + const WebKit::WebFilterOperations& filters() const { return m_filters; } + + void setBackgroundFilters(const WebKit::WebFilterOperations&); + const WebKit::WebFilterOperations& backgroundFilters() const { return m_backgroundFilters; } + + void setMasksToBounds(bool); + bool masksToBounds() const { return m_masksToBounds; } + + void setOpaque(bool); + bool opaque() const { return m_opaque; } + + void setOpacity(float); + bool opacityIsAnimating() const; + + void setPosition(const FloatPoint&); + const FloatPoint& position() const { return m_position; } + + void setIsContainerForFixedPositionLayers(bool isContainerForFixedPositionLayers) { m_isContainerForFixedPositionLayers = isContainerForFixedPositionLayers; } + bool isContainerForFixedPositionLayers() const { return m_isContainerForFixedPositionLayers; } + + void setFixedToContainerLayer(bool fixedToContainerLayer = true) { m_fixedToContainerLayer = fixedToContainerLayer;} + bool fixedToContainerLayer() const { return m_fixedToContainerLayer; } + + void setPreserves3D(bool); + bool preserves3D() const { return m_preserves3D; } + + void setUseParentBackfaceVisibility(bool useParentBackfaceVisibility) { m_useParentBackfaceVisibility = useParentBackfaceVisibility; } + bool useParentBackfaceVisibility() const { return m_useParentBackfaceVisibility; } + + void setUseLCDText(bool useLCDText) { m_useLCDText = useLCDText; } + bool useLCDText() const { return m_useLCDText; } + + void setSublayerTransform(const WebKit::WebTransformationMatrix&); + const WebKit::WebTransformationMatrix& sublayerTransform() const { return m_sublayerTransform; } + + // Debug layer border - visual effect only, do not change geometry/clipping/etc. + void setDebugBorderColor(SkColor); + SkColor debugBorderColor() const { return m_debugBorderColor; } + void setDebugBorderWidth(float); + float debugBorderWidth() const { return m_debugBorderWidth; } + bool hasDebugBorders() const; + + // Debug layer name. + void setDebugName(const String& debugName) { m_debugName = debugName; } + String debugName() const { return m_debugName; } + + CCRenderSurface* renderSurface() const { return m_renderSurface.get(); } + void createRenderSurface(); + void clearRenderSurface() { m_renderSurface.clear(); } + + float drawOpacity() const { return m_drawOpacity; } + void setDrawOpacity(float opacity) { m_drawOpacity = opacity; } + + bool drawOpacityIsAnimating() const { return m_drawOpacityIsAnimating; } + void setDrawOpacityIsAnimating(bool drawOpacityIsAnimating) { m_drawOpacityIsAnimating = drawOpacityIsAnimating; } + + CCLayerImpl* renderTarget() const { ASSERT(!m_renderTarget || m_renderTarget->renderSurface()); return m_renderTarget; } + void setRenderTarget(CCLayerImpl* target) { m_renderTarget = target; } + + void setBounds(const IntSize&); + const IntSize& bounds() const { return m_bounds; } + + const IntSize& contentBounds() const { return m_contentBounds; } + void setContentBounds(const IntSize&); + + const IntPoint& scrollPosition() const { return m_scrollPosition; } + void setScrollPosition(const IntPoint&); + + const IntSize& maxScrollPosition() const {return m_maxScrollPosition; } + void setMaxScrollPosition(const IntSize&); + + const FloatSize& scrollDelta() const { return m_scrollDelta; } + void setScrollDelta(const FloatSize&); + + float pageScaleDelta() const { return m_pageScaleDelta; } + void setPageScaleDelta(float); + + const IntSize& sentScrollDelta() const { return m_sentScrollDelta; } + void setSentScrollDelta(const IntSize& sentScrollDelta) { m_sentScrollDelta = sentScrollDelta; } + + void scrollBy(const FloatSize& scroll); + + bool scrollable() const { return m_scrollable; } + void setScrollable(bool scrollable) { m_scrollable = scrollable; } + + bool shouldScrollOnMainThread() const { return m_shouldScrollOnMainThread; } + void setShouldScrollOnMainThread(bool shouldScrollOnMainThread) { m_shouldScrollOnMainThread = shouldScrollOnMainThread; } + + bool haveWheelEventHandlers() const { return m_haveWheelEventHandlers; } + void setHaveWheelEventHandlers(bool haveWheelEventHandlers) { m_haveWheelEventHandlers = haveWheelEventHandlers; } + + const Region& nonFastScrollableRegion() const { return m_nonFastScrollableRegion; } + void setNonFastScrollableRegion(const Region& region) { m_nonFastScrollableRegion = region; } + + void setDrawCheckerboardForMissingTiles(bool checkerboard) { m_drawCheckerboardForMissingTiles = checkerboard; } + bool drawCheckerboardForMissingTiles() const { return m_drawCheckerboardForMissingTiles; } + + CCInputHandlerClient::ScrollStatus tryScroll(const IntPoint& viewportPoint, CCInputHandlerClient::ScrollInputType) const; + + const IntRect& visibleContentRect() const { return m_visibleContentRect; } + void setVisibleContentRect(const IntRect& visibleContentRect) { m_visibleContentRect = visibleContentRect; } + + bool doubleSided() const { return m_doubleSided; } + void setDoubleSided(bool); + + void setTransform(const WebKit::WebTransformationMatrix&); + bool transformIsAnimating() const; + + const WebKit::WebTransformationMatrix& drawTransform() const { return m_drawTransform; } + void setDrawTransform(const WebKit::WebTransformationMatrix& matrix) { m_drawTransform = matrix; } + const WebKit::WebTransformationMatrix& screenSpaceTransform() const { return m_screenSpaceTransform; } + void setScreenSpaceTransform(const WebKit::WebTransformationMatrix& matrix) { m_screenSpaceTransform = matrix; } + + bool drawTransformIsAnimating() const { return m_drawTransformIsAnimating; } + void setDrawTransformIsAnimating(bool animating) { m_drawTransformIsAnimating = animating; } + bool screenSpaceTransformIsAnimating() const { return m_screenSpaceTransformIsAnimating; } + void setScreenSpaceTransformIsAnimating(bool animating) { m_screenSpaceTransformIsAnimating = animating; } + + const IntRect& drawableContentRect() const { return m_drawableContentRect; } + void setDrawableContentRect(const IntRect& rect) { m_drawableContentRect = rect; } + const FloatRect& updateRect() const { return m_updateRect; } + void setUpdateRect(const FloatRect& updateRect) { m_updateRect = updateRect; } + + String layerTreeAsText() const; + + void setStackingOrderChanged(bool); + + bool layerPropertyChanged() const { return m_layerPropertyChanged || layerIsAlwaysDamaged(); } + bool layerSurfacePropertyChanged() const; + + void resetAllChangeTrackingForSubtree(); + + virtual bool layerIsAlwaysDamaged() const { return false; } + + CCLayerAnimationController* layerAnimationController() { return m_layerAnimationController.get(); } + + virtual Region visibleContentOpaqueRegion() const; + + // Indicates that the context previously used to render this layer + // was lost and that a new one has been created. Won't be called + // until the new context has been created successfully. + virtual void didLoseContext(); + + CCScrollbarAnimationController* scrollbarAnimationController() const { return m_scrollbarAnimationController.get(); } + + CCScrollbarLayerImpl* horizontalScrollbarLayer() const; + void setHorizontalScrollbarLayer(CCScrollbarLayerImpl*); + + CCScrollbarLayerImpl* verticalScrollbarLayer() const; + void setVerticalScrollbarLayer(CCScrollbarLayerImpl*); + +protected: + explicit CCLayerImpl(int); + + void appendDebugBorderQuad(CCQuadSink&, const CCSharedQuadState*) const; + + virtual void dumpLayerProperties(TextStream&, int indent) const; + static void writeIndent(TextStream&, int indent); + +private: + void setParent(CCLayerImpl* parent) { m_parent = parent; } + friend class TreeSynchronizer; + void clearChildList(); // Warning: This does not preserve tree structure invariants and so is only exposed to the tree synchronizer. + + void noteLayerPropertyChangedForSubtree(); + + // Note carefully this does not affect the current layer. + void noteLayerPropertyChangedForDescendants(); + + virtual const char* layerTypeAsString() const { return "LayerChromium"; } + + void dumpLayer(TextStream&, int indent) const; + + // Properties internal to CCLayerImpl + CCLayerImpl* m_parent; + Vector<OwnPtr<CCLayerImpl> > m_children; + // m_maskLayer can be temporarily stolen during tree sync, we need this ID to confirm newly assigned layer is still the previous one + int m_maskLayerId; + OwnPtr<CCLayerImpl> m_maskLayer; + int m_replicaLayerId; // ditto + OwnPtr<CCLayerImpl> m_replicaLayer; + int m_layerId; + CCLayerTreeHostImpl* m_layerTreeHostImpl; + + // Properties synchronized from the associated LayerChromium. + FloatPoint m_anchorPoint; + float m_anchorPointZ; + IntSize m_bounds; + IntSize m_contentBounds; + IntPoint m_scrollPosition; + bool m_scrollable; + bool m_shouldScrollOnMainThread; + bool m_haveWheelEventHandlers; + Region m_nonFastScrollableRegion; + SkColor m_backgroundColor; + + // Whether the "back" of this layer should draw. + bool m_doubleSided; + + // Tracks if drawing-related properties have changed since last redraw. + bool m_layerPropertyChanged; + + // Indicates that a property has changed on this layer that would not + // affect the pixels on its target surface, but would require redrawing + // but would require redrawing the targetSurface onto its ancestor targetSurface. + // For layers that do not own a surface this flag acts as m_layerPropertyChanged. + bool m_layerSurfacePropertyChanged; + + // Uses layer's content space. + IntRect m_visibleContentRect; + bool m_masksToBounds; + bool m_opaque; + float m_opacity; + FloatPoint m_position; + bool m_preserves3D; + bool m_useParentBackfaceVisibility; + bool m_drawCheckerboardForMissingTiles; + WebKit::WebTransformationMatrix m_sublayerTransform; + WebKit::WebTransformationMatrix m_transform; + bool m_useLCDText; + + bool m_drawsContent; + bool m_forceRenderSurface; + + // Set for the layer that other layers are fixed to. + bool m_isContainerForFixedPositionLayers; + // This is true if the layer should be fixed to the closest ancestor container. + bool m_fixedToContainerLayer; + + FloatSize m_scrollDelta; + IntSize m_sentScrollDelta; + IntSize m_maxScrollPosition; + float m_pageScaleDelta; + + // The layer whose coordinate space this layer draws into. This can be + // either the same layer (m_renderTarget == this) or an ancestor of this + // layer. + CCLayerImpl* m_renderTarget; + + // The global depth value of the center of the layer. This value is used + // to sort layers from back to front. + float m_drawDepth; + float m_drawOpacity; + bool m_drawOpacityIsAnimating; + + // Debug borders. + SkColor m_debugBorderColor; + float m_debugBorderWidth; + + // Debug layer name. + String m_debugName; + + WebKit::WebFilterOperations m_filters; + WebKit::WebFilterOperations m_backgroundFilters; + + WebKit::WebTransformationMatrix m_drawTransform; + WebKit::WebTransformationMatrix m_screenSpaceTransform; + bool m_drawTransformIsAnimating; + bool m_screenSpaceTransformIsAnimating; + +#ifndef NDEBUG + bool m_betweenWillDrawAndDidDraw; +#endif + + // Render surface associated with this layer. The layer and its descendants + // will render to this surface. + OwnPtr<CCRenderSurface> m_renderSurface; + + // Hierarchical bounding rect containing the layer and its descendants. + // Uses target surface's space. + IntRect m_drawableContentRect; + + // Rect indicating what was repainted/updated during update. + // Note that plugin layers bypass this and leave it empty. + // Uses layer's content space. + FloatRect m_updateRect; + + // Manages animations for this layer. + OwnPtr<CCLayerAnimationController> m_layerAnimationController; + + // Manages scrollbars for this layer + OwnPtr<CCScrollbarAnimationController> m_scrollbarAnimationController; +}; + +void sortLayers(Vector<CCLayerImpl*>::iterator first, Vector<CCLayerImpl*>::iterator end, CCLayerSorter*); + +} + +#endif // CCLayerImpl_h diff --git a/cc/CCLayerImplTest.cpp b/cc/CCLayerImplTest.cpp new file mode 100644 index 0000000..cc4dfbb --- /dev/null +++ b/cc/CCLayerImplTest.cpp @@ -0,0 +1,160 @@ +// 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 "config.h" + +#include "CCLayerImpl.h" + +#include "CCSingleThreadProxy.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/WebFilterOperation.h> +#include <public/WebFilterOperations.h> + +using namespace WebKit; +using namespace WebCore; + +namespace { + +#define EXECUTE_AND_VERIFY_SUBTREE_CHANGED(codeToTest) \ + root->resetAllChangeTrackingForSubtree(); \ + codeToTest; \ + EXPECT_TRUE(root->layerPropertyChanged()); \ + EXPECT_TRUE(child->layerPropertyChanged()); \ + EXPECT_TRUE(grandChild->layerPropertyChanged()); \ + EXPECT_FALSE(root->layerSurfacePropertyChanged()) + + +#define EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(codeToTest) \ + root->resetAllChangeTrackingForSubtree(); \ + codeToTest; \ + EXPECT_FALSE(root->layerPropertyChanged()); \ + EXPECT_FALSE(child->layerPropertyChanged()); \ + EXPECT_FALSE(grandChild->layerPropertyChanged()); \ + EXPECT_FALSE(root->layerSurfacePropertyChanged()) + +#define EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(codeToTest) \ + root->resetAllChangeTrackingForSubtree(); \ + codeToTest; \ + EXPECT_TRUE(root->layerPropertyChanged()); \ + EXPECT_FALSE(child->layerPropertyChanged()); \ + EXPECT_FALSE(grandChild->layerPropertyChanged()); \ + EXPECT_FALSE(root->layerSurfacePropertyChanged()) + +#define EXECUTE_AND_VERIFY_ONLY_SURFACE_CHANGED(codeToTest) \ + root->resetAllChangeTrackingForSubtree(); \ + codeToTest; \ + EXPECT_FALSE(root->layerPropertyChanged()); \ + EXPECT_FALSE(child->layerPropertyChanged()); \ + EXPECT_FALSE(grandChild->layerPropertyChanged()); \ + EXPECT_TRUE(root->layerSurfacePropertyChanged()) + +TEST(CCLayerImplTest, verifyLayerChangesAreTrackedProperly) +{ + // + // This test checks that layerPropertyChanged() has the correct behavior. + // + + // The constructor on this will fake that we are on the correct thread. + DebugScopedSetImplThread setImplThread; + + // Create a simple CCLayerImpl tree: + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + root->addChild(CCLayerImpl::create(2)); + CCLayerImpl* child = root->children()[0].get(); + child->addChild(CCLayerImpl::create(3)); + CCLayerImpl* grandChild = child->children()[0].get(); + + // Adding children is an internal operation and should not mark layers as changed. + EXPECT_FALSE(root->layerPropertyChanged()); + EXPECT_FALSE(child->layerPropertyChanged()); + EXPECT_FALSE(grandChild->layerPropertyChanged()); + + FloatPoint arbitraryFloatPoint = FloatPoint(0.125f, 0.25f); + float arbitraryNumber = 0.352f; + IntSize arbitraryIntSize = IntSize(111, 222); + IntPoint arbitraryIntPoint = IntPoint(333, 444); + IntRect arbitraryIntRect = IntRect(arbitraryIntPoint, arbitraryIntSize); + FloatRect arbitraryFloatRect = FloatRect(arbitraryFloatPoint, FloatSize(1.234f, 5.678f)); + SkColor arbitraryColor = SkColorSetRGB(10, 20, 30); + WebTransformationMatrix arbitraryTransform; + arbitraryTransform.scale3d(0.1, 0.2, 0.3); + WebFilterOperations arbitraryFilters; + arbitraryFilters.append(WebFilterOperation::createOpacityFilter(0.5)); + + // These properties are internal, and should not be considered "change" when they are used. + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setUseLCDText(true)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setDrawOpacity(arbitraryNumber)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setRenderTarget(0)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setDrawTransform(arbitraryTransform)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setScreenSpaceTransform(arbitraryTransform)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setDrawableContentRect(arbitraryIntRect)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setUpdateRect(arbitraryFloatRect)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setVisibleContentRect(arbitraryIntRect)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setMaxScrollPosition(arbitraryIntSize)); + + // Changing these properties affects the entire subtree of layers. + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setAnchorPoint(arbitraryFloatPoint)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setAnchorPointZ(arbitraryNumber)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setFilters(arbitraryFilters)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setMaskLayer(CCLayerImpl::create(4))); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setMasksToBounds(true)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setOpaque(true)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setReplicaLayer(CCLayerImpl::create(5))); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setPosition(arbitraryFloatPoint)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setPreserves3D(true)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setDoubleSided(false)); // constructor initializes it to "true". + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->scrollBy(arbitraryIntSize)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setScrollDelta(IntSize())); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setScrollPosition(arbitraryIntPoint)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setPageScaleDelta(arbitraryNumber)); + + // Changing these properties only affects the layer itself. + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->setContentBounds(arbitraryIntSize)); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->setDebugBorderColor(arbitraryColor)); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->setDebugBorderWidth(arbitraryNumber)); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->setDrawsContent(true)); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->setBackgroundColor(SK_ColorGRAY)); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->setBackgroundFilters(arbitraryFilters)); + + // Changing these properties only affects how render surface is drawn + EXECUTE_AND_VERIFY_ONLY_SURFACE_CHANGED(root->setOpacity(arbitraryNumber)); + EXECUTE_AND_VERIFY_ONLY_SURFACE_CHANGED(root->setTransform(arbitraryTransform)); + + // Special case: check that sublayer transform changes all layer's descendants, but not the layer itself. + root->resetAllChangeTrackingForSubtree(); + root->setSublayerTransform(arbitraryTransform); + EXPECT_FALSE(root->layerPropertyChanged()); + EXPECT_TRUE(child->layerPropertyChanged()); + EXPECT_TRUE(grandChild->layerPropertyChanged()); + + // Special case: check that setBounds changes behavior depending on masksToBounds. + root->setMasksToBounds(false); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->setBounds(IntSize(135, 246))); + root->setMasksToBounds(true); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->setBounds(arbitraryIntSize)); // should be a different size than previous call, to ensure it marks tree changed. + + // After setting all these properties already, setting to the exact same values again should + // not cause any change. + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setAnchorPoint(arbitraryFloatPoint)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setAnchorPointZ(arbitraryNumber)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setMasksToBounds(true)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setPosition(arbitraryFloatPoint)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setPreserves3D(true)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setTransform(arbitraryTransform)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setDoubleSided(false)); // constructor initializes it to "true". + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setScrollDelta(IntSize())); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setScrollPosition(arbitraryIntPoint)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setPageScaleDelta(arbitraryNumber)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setContentBounds(arbitraryIntSize)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setOpaque(true)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setOpacity(arbitraryNumber)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setDebugBorderColor(arbitraryColor)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setDebugBorderWidth(arbitraryNumber)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setDrawsContent(true)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setSublayerTransform(arbitraryTransform)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->setBounds(arbitraryIntSize)); +} + +} // namespace diff --git a/cc/CCLayerIterator.cpp b/cc/CCLayerIterator.cpp new file mode 100644 index 0000000..d7de9a5 --- /dev/null +++ b/cc/CCLayerIterator.cpp @@ -0,0 +1,150 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCLayerIterator.h" + +#include "CCLayerImpl.h" +#include "CCRenderSurface.h" +#include "LayerChromium.h" +#include "RenderSurfaceChromium.h" + +namespace WebCore { + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void CCLayerIteratorActions::BackToFront::begin(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + it.m_targetRenderSurfaceLayerIndex = 0; + it.m_currentLayerIndex = CCLayerIteratorValue::LayerIndexRepresentingTargetRenderSurface; + + m_highestTargetRenderSurfaceLayer = 0; +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void CCLayerIteratorActions::BackToFront::end(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + it.m_targetRenderSurfaceLayerIndex = CCLayerIteratorValue::InvalidTargetRenderSurfaceLayerIndex; + it.m_currentLayerIndex = 0; +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void CCLayerIteratorActions::BackToFront::next(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + // If the current layer has a RS, move to its layer list. Otherwise, visit the next layer in the current RS layer list. + if (it.currentLayerRepresentsContributingRenderSurface()) { + // Save our position in the childLayer list for the RenderSurface, then jump to the next RenderSurface. Save where we + // came from in the next RenderSurface so we can get back to it. + it.targetRenderSurface()->m_currentLayerIndexHistory = it.m_currentLayerIndex; + int previousTargetRenderSurfaceLayer = it.m_targetRenderSurfaceLayerIndex; + + it.m_targetRenderSurfaceLayerIndex = ++m_highestTargetRenderSurfaceLayer; + it.m_currentLayerIndex = CCLayerIteratorValue::LayerIndexRepresentingTargetRenderSurface; + + it.targetRenderSurface()->m_targetRenderSurfaceLayerIndexHistory = previousTargetRenderSurfaceLayer; + } else { + ++it.m_currentLayerIndex; + + int targetRenderSurfaceNumChildren = it.targetRenderSurfaceChildren().size(); + while (it.m_currentLayerIndex == targetRenderSurfaceNumChildren) { + // Jump back to the previous RenderSurface, and get back the position where we were in that list, and move to the next position there. + if (!it.m_targetRenderSurfaceLayerIndex) { + // End of the list + it.m_targetRenderSurfaceLayerIndex = CCLayerIteratorValue::InvalidTargetRenderSurfaceLayerIndex; + it.m_currentLayerIndex = 0; + return; + } + it.m_targetRenderSurfaceLayerIndex = it.targetRenderSurface()->m_targetRenderSurfaceLayerIndexHistory; + it.m_currentLayerIndex = it.targetRenderSurface()->m_currentLayerIndexHistory + 1; + + targetRenderSurfaceNumChildren = it.targetRenderSurfaceChildren().size(); + } + } +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void CCLayerIteratorActions::FrontToBack::begin(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + it.m_targetRenderSurfaceLayerIndex = 0; + it.m_currentLayerIndex = it.targetRenderSurfaceChildren().size() - 1; + goToHighestInSubtree(it); +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void CCLayerIteratorActions::FrontToBack::end(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + it.m_targetRenderSurfaceLayerIndex = CCLayerIteratorValue::InvalidTargetRenderSurfaceLayerIndex; + it.m_currentLayerIndex = 0; +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void CCLayerIteratorActions::FrontToBack::next(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + // Moves to the previous layer in the current RS layer list. Then we check if the + // new current layer has its own RS, in which case there are things in that RS layer list that are higher, so + // we find the highest layer in that subtree. + // If we move back past the front of the list, we jump up to the previous RS layer list, picking up again where we + // had previously recursed into the current RS layer list. + + if (!it.currentLayerRepresentsTargetRenderSurface()) { + // Subtracting one here will eventually cause the current layer to become that layer + // representing the target render surface. + --it.m_currentLayerIndex; + goToHighestInSubtree(it); + } else { + while (it.currentLayerRepresentsTargetRenderSurface()) { + if (!it.m_targetRenderSurfaceLayerIndex) { + // End of the list + it.m_targetRenderSurfaceLayerIndex = CCLayerIteratorValue::InvalidTargetRenderSurfaceLayerIndex; + it.m_currentLayerIndex = 0; + return; + } + it.m_targetRenderSurfaceLayerIndex = it.targetRenderSurface()->m_targetRenderSurfaceLayerIndexHistory; + it.m_currentLayerIndex = it.targetRenderSurface()->m_currentLayerIndexHistory; + } + } +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void CCLayerIteratorActions::FrontToBack::goToHighestInSubtree(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + if (it.currentLayerRepresentsTargetRenderSurface()) + return; + while (it.currentLayerRepresentsContributingRenderSurface()) { + // Save where we were in the current target surface, move to the next one, and save the target surface that we + // came from there so we can go back to it. + it.targetRenderSurface()->m_currentLayerIndexHistory = it.m_currentLayerIndex; + int previousTargetRenderSurfaceLayer = it.m_targetRenderSurfaceLayerIndex; + + for (LayerType* layer = it.currentLayer(); it.targetRenderSurfaceLayer() != layer; ++it.m_targetRenderSurfaceLayerIndex) { } + it.m_currentLayerIndex = it.targetRenderSurfaceChildren().size() - 1; + + it.targetRenderSurface()->m_targetRenderSurfaceLayerIndexHistory = previousTargetRenderSurfaceLayer; + } +} + +// Declare each of the above functions for LayerChromium and CCLayerImpl classes so that they are linked. +template void CCLayerIteratorActions::BackToFront::begin(CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, BackToFront> &); +template void CCLayerIteratorActions::BackToFront::end(CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, BackToFront>&); +template void CCLayerIteratorActions::BackToFront::next(CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, BackToFront>&); + +template void CCLayerIteratorActions::BackToFront::begin(CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, BackToFront>&); +template void CCLayerIteratorActions::BackToFront::end(CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, BackToFront>&); +template void CCLayerIteratorActions::BackToFront::next(CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, BackToFront>&); + +template void CCLayerIteratorActions::FrontToBack::next(CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, FrontToBack>&); +template void CCLayerIteratorActions::FrontToBack::end(CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, FrontToBack>&); +template void CCLayerIteratorActions::FrontToBack::begin(CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, FrontToBack>&); +template void CCLayerIteratorActions::FrontToBack::goToHighestInSubtree(CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, FrontToBack>&); + +template void CCLayerIteratorActions::FrontToBack::next(CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, FrontToBack>&); +template void CCLayerIteratorActions::FrontToBack::end(CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, FrontToBack>&); +template void CCLayerIteratorActions::FrontToBack::begin(CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, FrontToBack>&); +template void CCLayerIteratorActions::FrontToBack::goToHighestInSubtree(CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, FrontToBack>&); + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCLayerIterator.h b/cc/CCLayerIterator.h new file mode 100644 index 0000000..907182d --- /dev/null +++ b/cc/CCLayerIterator.h @@ -0,0 +1,210 @@ +// Copyright 2012 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. + +#ifndef CCLayerIterator_h +#define CCLayerIterator_h + +#include "CCLayerTreeHostCommon.h" + +#include <wtf/PassOwnPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +// These classes provide means to iterate over the RenderSurface-Layer tree. + +// Example code follows, for a tree of LayerChromium/RenderSurfaceChromium objects. See below for details. +// +// void doStuffOnLayers(const Vector<RefPtr<LayerChromium> >& renderSurfaceLayerList) +// { +// typedef CCLayerIterator<LayerChromium, RenderSurfaceChromium, CCLayerIteratorActions::FrontToBack> CCLayerIteratorType; +// +// CCLayerIteratorType end = CCLayerIteratorType::end(&renderSurfaceLayerList); +// for (CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); it != end; ++it) { +// // Only one of these will be true +// if (it.representsTargetRenderSurface()) +// foo(*it); // *it is a layer representing a target RenderSurface +// if (it.representsContributingRenderSurface()) +// bar(*it); // *it is a layer representing a RenderSurface that contributes to the layer's target RenderSurface +// if (it.representsItself()) +// baz(*it); // *it is a layer representing itself, as it contributes to its own target RenderSurface +// } +// } + +// A RenderSurface R may be referred to in one of two different contexts. One RenderSurface is "current" at any time, for +// whatever operation is being performed. This current surface is referred to as a target surface. For example, when R is +// being painted it would be the target surface. Once R has been painted, its contents may be included into another +// surface S. While S is considered the target surface when it is being painted, R is called a contributing surface +// in this context as it contributes to the content of the target surface S. +// +// The iterator's current position in the tree always points to some layer. The state of the iterator indicates the role of the +// layer, and will be one of the following three states. A single layer L will appear in the iteration process in at least one, +// and possibly all, of these states. +// 1. Representing the target surface: The iterator in this state, pointing at layer L, indicates that the target RenderSurface +// is now the surface owned by L. This will occur exactly once for each RenderSurface in the tree. +// 2. Representing a contributing surface: The iterator in this state, pointing at layer L, refers to the RenderSurface owned +// by L as a contributing surface, without changing the current target RenderSurface. +// 3. Representing itself: The iterator in this state, pointing at layer L, refers to the layer itself, as a child of the +// current target RenderSurface. +// +// The BackToFront iterator will return a layer representing the target surface before returning layers representing themselves +// as children of the current target surface. Whereas the FrontToBack ordering will iterate over children layers of a surface +// before the layer representing the surface as a target surface. +// +// To use the iterators: +// +// Create a stepping iterator and end iterator by calling CCLayerIterator::begin() and CCLayerIterator::end() and passing in the +// list of layers owning target RenderSurfaces. Step through the tree by incrementing the stepping iterator while it is != to +// the end iterator. At each step the iterator knows what the layer is representing, and you can query the iterator to decide +// what actions to perform with the layer given what it represents. + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Non-templated constants +struct CCLayerIteratorValue { + static const int InvalidTargetRenderSurfaceLayerIndex = -1; + // This must be -1 since the iterator action code assumes that this value can be + // reached by subtracting one from the position of the first layer in the current + // target surface's child layer list, which is 0. + static const int LayerIndexRepresentingTargetRenderSurface = -1; +}; + +// The position of a layer iterator that is independent of its many template types. +template <typename LayerType> +struct CCLayerIteratorPosition { + bool representsTargetRenderSurface; + bool representsContributingRenderSurface; + bool representsItself; + LayerType* targetRenderSurfaceLayer; + LayerType* currentLayer; +}; + +// An iterator class for walking over layers in the RenderSurface-Layer tree. +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename IteratorActionType> +class CCLayerIterator { + typedef CCLayerIterator<LayerType, LayerList, RenderSurfaceType, IteratorActionType> CCLayerIteratorType; + +public: + CCLayerIterator() : m_renderSurfaceLayerList(0) { } + + static CCLayerIteratorType begin(const LayerList* renderSurfaceLayerList) { return CCLayerIteratorType(renderSurfaceLayerList, true); } + static CCLayerIteratorType end(const LayerList* renderSurfaceLayerList) { return CCLayerIteratorType(renderSurfaceLayerList, false); } + + CCLayerIteratorType& operator++() { m_actions.next(*this); return *this; } + bool operator==(const CCLayerIterator& other) const + { + return m_targetRenderSurfaceLayerIndex == other.m_targetRenderSurfaceLayerIndex + && m_currentLayerIndex == other.m_currentLayerIndex; + } + bool operator!=(const CCLayerIteratorType& other) const { return !(*this == other); } + + LayerType* operator->() const { return currentLayer(); } + LayerType* operator*() const { return currentLayer(); } + + bool representsTargetRenderSurface() const { return currentLayerRepresentsTargetRenderSurface(); } + bool representsContributingRenderSurface() const { return !representsTargetRenderSurface() && currentLayerRepresentsContributingRenderSurface(); } + bool representsItself() const { return !representsTargetRenderSurface() && !representsContributingRenderSurface(); } + + LayerType* targetRenderSurfaceLayer() const { return getRawPtr((*m_renderSurfaceLayerList)[m_targetRenderSurfaceLayerIndex]); } + + operator const CCLayerIteratorPosition<LayerType>() const + { + CCLayerIteratorPosition<LayerType> position; + position.representsTargetRenderSurface = representsTargetRenderSurface(); + position.representsContributingRenderSurface = representsContributingRenderSurface(); + position.representsItself = representsItself(); + position.targetRenderSurfaceLayer = targetRenderSurfaceLayer(); + position.currentLayer = currentLayer(); + return position; + } + +private: + CCLayerIterator(const LayerList* renderSurfaceLayerList, bool start) + : m_renderSurfaceLayerList(renderSurfaceLayerList) + , m_targetRenderSurfaceLayerIndex(0) + { + for (size_t i = 0; i < renderSurfaceLayerList->size(); ++i) { + if (!(*renderSurfaceLayerList)[i]->renderSurface()) { + ASSERT_NOT_REACHED(); + m_actions.end(*this); + return; + } + } + + if (start && !renderSurfaceLayerList->isEmpty()) + m_actions.begin(*this); + else + m_actions.end(*this); + } + + inline static LayerChromium* getRawPtr(const RefPtr<LayerChromium>& ptr) { return ptr.get(); } + inline static CCLayerImpl* getRawPtr(CCLayerImpl* ptr) { return ptr; } + + inline LayerType* currentLayer() const { return currentLayerRepresentsTargetRenderSurface() ? targetRenderSurfaceLayer() : getRawPtr(targetRenderSurfaceChildren()[m_currentLayerIndex]); } + + inline bool currentLayerRepresentsContributingRenderSurface() const { return CCLayerTreeHostCommon::renderSurfaceContributesToTarget<LayerType>(currentLayer(), targetRenderSurfaceLayer()->id()); } + inline bool currentLayerRepresentsTargetRenderSurface() const { return m_currentLayerIndex == CCLayerIteratorValue::LayerIndexRepresentingTargetRenderSurface; } + + inline RenderSurfaceType* targetRenderSurface() const { return targetRenderSurfaceLayer()->renderSurface(); } + inline const LayerList& targetRenderSurfaceChildren() const { return targetRenderSurface()->layerList(); } + + IteratorActionType m_actions; + const LayerList* m_renderSurfaceLayerList; + + // The iterator's current position. + + // A position in the renderSurfaceLayerList. This points to a layer which owns the current target surface. + // This is a value from 0 to n-1 (n = size of renderSurfaceLayerList = number of surfaces). A value outside of + // this range (for example, CCLayerIteratorValue::InvalidTargetRenderSurfaceLayerIndex) is used to + // indicate a position outside the bounds of the tree. + int m_targetRenderSurfaceLayerIndex; + // A position in the list of layers that are children of the current target surface. When pointing to one of + // these layers, this is a value from 0 to n-1 (n = number of children). Since the iterator must also stop at + // the layers representing the target surface, this is done by setting the currentLayerIndex to a value of + // CCLayerIteratorValue::LayerRepresentingTargetRenderSurface. + int m_currentLayerIndex; + + friend struct CCLayerIteratorActions; +}; + +// Orderings for iterating over the RenderSurface-Layer tree. +struct CCLayerIteratorActions { + // Walks layers sorted by z-order from back to front. + class BackToFront { + public: + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void begin(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void end(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void next(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + private: + int m_highestTargetRenderSurfaceLayer; + }; + + // Walks layers sorted by z-order from front to back + class FrontToBack { + public: + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void begin(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void end(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void next(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + private: + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void goToHighestInSubtree(CCLayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + }; +}; + +} // namespace WebCore + +#endif diff --git a/cc/CCLayerIteratorTest.cpp b/cc/CCLayerIteratorTest.cpp new file mode 100644 index 0000000..bc5b2f3 --- /dev/null +++ b/cc/CCLayerIteratorTest.cpp @@ -0,0 +1,255 @@ +// Copyright 2012 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 "CCLayerIterator.h" + +#include "CCLayerTreeHostCommon.h" +#include "LayerChromium.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/WebTransformationMatrix.h> + +using namespace WebCore; +using WebKit::WebTransformationMatrix; +using ::testing::Mock; +using ::testing::_; +using ::testing::AtLeast; +using ::testing::AnyNumber; + +namespace { + +class TestLayerChromium : public LayerChromium { +public: + static PassRefPtr<TestLayerChromium> create() { return adoptRef(new TestLayerChromium()); } + + int m_countRepresentingTargetSurface; + int m_countRepresentingContributingSurface; + int m_countRepresentingItself; + + virtual bool drawsContent() const OVERRIDE { return m_drawsContent; } + void setDrawsContent(bool drawsContent) { m_drawsContent = drawsContent; } + +private: + TestLayerChromium() + : LayerChromium() + , m_drawsContent(true) + { + setBounds(IntSize(100, 100)); + setPosition(IntPoint::zero()); + setAnchorPoint(IntPoint::zero()); + } + + bool m_drawsContent; +}; + +#define EXPECT_COUNT(layer, target, contrib, itself) \ + EXPECT_EQ(target, layer->m_countRepresentingTargetSurface); \ + EXPECT_EQ(contrib, layer->m_countRepresentingContributingSurface); \ + EXPECT_EQ(itself, layer->m_countRepresentingItself); + +typedef CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, CCLayerIteratorActions::FrontToBack> FrontToBack; +typedef CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, CCLayerIteratorActions::BackToFront> BackToFront; + +void resetCounts(Vector<RefPtr<LayerChromium> >& renderSurfaceLayerList) +{ + for (unsigned surfaceIndex = 0; surfaceIndex < renderSurfaceLayerList.size(); ++surfaceIndex) { + TestLayerChromium* renderSurfaceLayer = static_cast<TestLayerChromium*>(renderSurfaceLayerList[surfaceIndex].get()); + RenderSurfaceChromium* renderSurface = renderSurfaceLayer->renderSurface(); + + renderSurfaceLayer->m_countRepresentingTargetSurface = -1; + renderSurfaceLayer->m_countRepresentingContributingSurface = -1; + renderSurfaceLayer->m_countRepresentingItself = -1; + + for (unsigned layerIndex = 0; layerIndex < renderSurface->layerList().size(); ++layerIndex) { + TestLayerChromium* layer = static_cast<TestLayerChromium*>(renderSurface->layerList()[layerIndex].get()); + + layer->m_countRepresentingTargetSurface = -1; + layer->m_countRepresentingContributingSurface = -1; + layer->m_countRepresentingItself = -1; + } + } +} + +void iterateFrontToBack(Vector<RefPtr<LayerChromium> >* renderSurfaceLayerList) +{ + resetCounts(*renderSurfaceLayerList); + int count = 0; + for (FrontToBack it = FrontToBack::begin(renderSurfaceLayerList); it != FrontToBack::end(renderSurfaceLayerList); ++it, ++count) { + TestLayerChromium* layer = static_cast<TestLayerChromium*>(*it); + if (it.representsTargetRenderSurface()) + layer->m_countRepresentingTargetSurface = count; + if (it.representsContributingRenderSurface()) + layer->m_countRepresentingContributingSurface = count; + if (it.representsItself()) + layer->m_countRepresentingItself = count; + } +} + +void iterateBackToFront(Vector<RefPtr<LayerChromium> >* renderSurfaceLayerList) +{ + resetCounts(*renderSurfaceLayerList); + int count = 0; + for (BackToFront it = BackToFront::begin(renderSurfaceLayerList); it != BackToFront::end(renderSurfaceLayerList); ++it, ++count) { + TestLayerChromium* layer = static_cast<TestLayerChromium*>(*it); + if (it.representsTargetRenderSurface()) + layer->m_countRepresentingTargetSurface = count; + if (it.representsContributingRenderSurface()) + layer->m_countRepresentingContributingSurface = count; + if (it.representsItself()) + layer->m_countRepresentingItself = count; + } +} + +TEST(CCLayerIteratorTest, emptyTree) +{ + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + + iterateBackToFront(&renderSurfaceLayerList); + iterateFrontToBack(&renderSurfaceLayerList); +} + +TEST(CCLayerIteratorTest, simpleTree) +{ + RefPtr<TestLayerChromium> rootLayer = TestLayerChromium::create(); + RefPtr<TestLayerChromium> first = TestLayerChromium::create(); + RefPtr<TestLayerChromium> second = TestLayerChromium::create(); + RefPtr<TestLayerChromium> third = TestLayerChromium::create(); + RefPtr<TestLayerChromium> fourth = TestLayerChromium::create(); + + rootLayer->createRenderSurface(); + + rootLayer->addChild(first); + rootLayer->addChild(second); + rootLayer->addChild(third); + rootLayer->addChild(fourth); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + CCLayerTreeHostCommon::calculateDrawTransforms(rootLayer.get(), rootLayer->bounds(), 1, 256, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + iterateBackToFront(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 0, -1, 1); + EXPECT_COUNT(first, -1, -1, 2); + EXPECT_COUNT(second, -1, -1, 3); + EXPECT_COUNT(third, -1, -1, 4); + EXPECT_COUNT(fourth, -1, -1, 5); + + iterateFrontToBack(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 5, -1, 4); + EXPECT_COUNT(first, -1, -1, 3); + EXPECT_COUNT(second, -1, -1, 2); + EXPECT_COUNT(third, -1, -1, 1); + EXPECT_COUNT(fourth, -1, -1, 0); + +} + +TEST(CCLayerIteratorTest, complexTree) +{ + RefPtr<TestLayerChromium> rootLayer = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root1 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root2 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root3 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root21 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root22 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root23 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root221 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root231 = TestLayerChromium::create(); + + rootLayer->createRenderSurface(); + + rootLayer->addChild(root1); + rootLayer->addChild(root2); + rootLayer->addChild(root3); + root2->addChild(root21); + root2->addChild(root22); + root2->addChild(root23); + root22->addChild(root221); + root23->addChild(root231); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + CCLayerTreeHostCommon::calculateDrawTransforms(rootLayer.get(), rootLayer->bounds(), 1, 256, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + iterateBackToFront(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 0, -1, 1); + EXPECT_COUNT(root1, -1, -1, 2); + EXPECT_COUNT(root2, -1, -1, 3); + EXPECT_COUNT(root21, -1, -1, 4); + EXPECT_COUNT(root22, -1, -1, 5); + EXPECT_COUNT(root221, -1, -1, 6); + EXPECT_COUNT(root23, -1, -1, 7); + EXPECT_COUNT(root231, -1, -1, 8); + EXPECT_COUNT(root3, -1, -1, 9); + + iterateFrontToBack(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 9, -1, 8); + EXPECT_COUNT(root1, -1, -1, 7); + EXPECT_COUNT(root2, -1, -1, 6); + EXPECT_COUNT(root21, -1, -1, 5); + EXPECT_COUNT(root22, -1, -1, 4); + EXPECT_COUNT(root221, -1, -1, 3); + EXPECT_COUNT(root23, -1, -1, 2); + EXPECT_COUNT(root231, -1, -1, 1); + EXPECT_COUNT(root3, -1, -1, 0); + +} + +TEST(CCLayerIteratorTest, complexTreeMultiSurface) +{ + RefPtr<TestLayerChromium> rootLayer = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root1 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root2 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root3 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root21 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root22 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root23 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root221 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> root231 = TestLayerChromium::create(); + + rootLayer->createRenderSurface(); + rootLayer->renderSurface()->setContentRect(IntRect(IntPoint(), rootLayer->bounds())); + + rootLayer->addChild(root1); + rootLayer->addChild(root2); + rootLayer->addChild(root3); + root2->setDrawsContent(false); + root2->setOpacity(0.5); // Force the layer to own a new surface. + root2->addChild(root21); + root2->addChild(root22); + root2->addChild(root23); + root22->setOpacity(0.5); + root22->addChild(root221); + root23->setOpacity(0.5); + root23->addChild(root231); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + CCLayerTreeHostCommon::calculateDrawTransforms(rootLayer.get(), rootLayer->bounds(), 1, 256, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + iterateBackToFront(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 0, -1, 1); + EXPECT_COUNT(root1, -1, -1, 2); + EXPECT_COUNT(root2, 4, 3, -1); + EXPECT_COUNT(root21, -1, -1, 5); + EXPECT_COUNT(root22, 7, 6, 8); + EXPECT_COUNT(root221, -1, -1, 9); + EXPECT_COUNT(root23, 11, 10, 12); + EXPECT_COUNT(root231, -1, -1, 13); + EXPECT_COUNT(root3, -1, -1, 14); + + iterateFrontToBack(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 14, -1, 13); + EXPECT_COUNT(root1, -1, -1, 12); + EXPECT_COUNT(root2, 10, 11, -1); + EXPECT_COUNT(root21, -1, -1, 9); + EXPECT_COUNT(root22, 7, 8, 6); + EXPECT_COUNT(root221, -1, -1, 5); + EXPECT_COUNT(root23, 3, 4, 2); + EXPECT_COUNT(root231, -1, -1, 1); + EXPECT_COUNT(root3, -1, -1, 0); +} + +} // namespace diff --git a/cc/CCLayerQuad.cpp b/cc/CCLayerQuad.cpp new file mode 100644 index 0000000..c6dfcc4 --- /dev/null +++ b/cc/CCLayerQuad.cpp @@ -0,0 +1,66 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCLayerQuad.h" + +namespace WebCore { + +CCLayerQuad::Edge::Edge(const FloatPoint& p, const FloatPoint& q) +{ + ASSERT(p != q); + + FloatPoint tangent(p.y() - q.y(), q.x() - p.x()); + float cross2 = p.x() * q.y() - q.x() * p.y(); + + set(tangent.x(), tangent.y(), cross2); + scale(1.0f / tangent.length()); +} + +CCLayerQuad::CCLayerQuad(const FloatQuad& quad) +{ + // Create edges. + m_left = Edge(quad.p4(), quad.p1()); + m_right = Edge(quad.p2(), quad.p3()); + m_top = Edge(quad.p1(), quad.p2()); + m_bottom = Edge(quad.p3(), quad.p4()); + + float sign = quad.isCounterclockwise() ? -1 : 1; + m_left.scale(sign); + m_right.scale(sign); + m_top.scale(sign); + m_bottom.scale(sign); +} + +FloatQuad CCLayerQuad::floatQuad() const +{ + return FloatQuad(m_left.intersect(m_top), + m_top.intersect(m_right), + m_right.intersect(m_bottom), + m_bottom.intersect(m_left)); +} + +void CCLayerQuad::toFloatArray(float flattened[12]) const +{ + flattened[0] = m_left.x(); + flattened[1] = m_left.y(); + flattened[2] = m_left.z(); + flattened[3] = m_top.x(); + flattened[4] = m_top.y(); + flattened[5] = m_top.z(); + flattened[6] = m_right.x(); + flattened[7] = m_right.y(); + flattened[8] = m_right.z(); + flattened[9] = m_bottom.x(); + flattened[10] = m_bottom.y(); + flattened[11] = m_bottom.z(); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCLayerQuad.h b/cc/CCLayerQuad.h new file mode 100644 index 0000000..98688fb --- /dev/null +++ b/cc/CCLayerQuad.h @@ -0,0 +1,112 @@ +// 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. + + +#ifndef CCLayerQuad_h +#define CCLayerQuad_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "FloatPoint3D.h" +#include "FloatQuad.h" + +static const float kAntiAliasingInflateDistance = 0.5f; + +namespace WebCore { + +class CCLayerQuad { +public: + class Edge { + public: + Edge() + : m_x(0) + , m_y(0) + , m_z(0) + { + } + Edge(const FloatPoint&, const FloatPoint&); + + float x() const { return m_x; } + float y() const { return m_y; } + float z() const { return m_z; } + + void setX(float x) { m_x = x; } + void setY(float y) { m_y = y; } + void setZ(float z) { m_z = z; } + void set(float x, float y, float z) + { + m_x = x; + m_y = y; + m_z = z; + } + + void moveX(float dx) { m_x += dx; } + void moveY(float dy) { m_y += dy; } + void moveZ(float dz) { m_z += dz; } + void move(float dx, float dy, float dz) + { + m_x += dx; + m_y += dy; + m_z += dz; + } + + void scaleX(float sx) { m_x *= sx; } + void scaleY(float sy) { m_y *= sy; } + void scaleZ(float sz) { m_z *= sz; } + void scale(float sx, float sy, float sz) + { + m_x *= sx; + m_y *= sy; + m_z *= sz; + } + void scale(float s) { scale(s, s, s); } + + FloatPoint intersect(const Edge& e) const + { + return FloatPoint( + (y() * e.z() - e.y() * z()) / (x() * e.y() - e.x() * y()), + (x() * e.z() - e.x() * z()) / (e.x() * y() - x() * e.y())); + } + + private: + float m_x; + float m_y; + float m_z; + }; + + CCLayerQuad(const Edge& left, const Edge& top, const Edge& right, const Edge& bottom) + : m_left(left) + , m_top(top) + , m_right(right) + , m_bottom(bottom) + { + } + CCLayerQuad(const FloatQuad&); + + Edge left() const { return m_left; } + Edge top() const { return m_top; } + Edge right() const { return m_right; } + Edge bottom() const { return m_bottom; } + + void inflateX(float dx) { m_left.moveZ(dx); m_right.moveZ(dx); } + void inflateY(float dy) { m_top.moveZ(dy); m_bottom.moveZ(dy); } + void inflate(float d) { inflateX(d); inflateY(d); } + void inflateAntiAliasingDistance() { inflate(kAntiAliasingInflateDistance); } + + FloatQuad floatQuad() const; + + void toFloatArray(float[12]) const; + +private: + Edge m_left; + Edge m_top; + Edge m_right; + Edge m_bottom; +}; + +} + +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/CCLayerQuadTest.cpp b/cc/CCLayerQuadTest.cpp new file mode 100644 index 0000000..eb4a1f0 --- /dev/null +++ b/cc/CCLayerQuadTest.cpp @@ -0,0 +1,45 @@ +// 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 "config.h" + +#include "CCLayerQuad.h" + +#include <gtest/gtest.h> + +using namespace WebCore; + +namespace { + +TEST(CCLayerQuadTest, FloatQuadConversion) +{ + FloatPoint p1(-0.5, -0.5); + FloatPoint p2( 0.5, -0.5); + FloatPoint p3( 0.5, 0.5); + FloatPoint p4(-0.5, 0.5); + + FloatQuad quadCW(p1, p2, p3, p4); + CCLayerQuad layerQuadCW(quadCW); + EXPECT_TRUE(layerQuadCW.floatQuad() == quadCW); + + FloatQuad quadCCW(p1, p4, p3, p2); + CCLayerQuad layerQuadCCW(quadCCW); + EXPECT_TRUE(layerQuadCCW.floatQuad() == quadCCW); +} + +TEST(CCLayerQuadTest, Inflate) +{ + FloatPoint p1(-0.5, -0.5); + FloatPoint p2( 0.5, -0.5); + FloatPoint p3( 0.5, 0.5); + FloatPoint p4(-0.5, 0.5); + + FloatQuad quad(p1, p2, p3, p4); + CCLayerQuad layerQuad(quad); + quad.scale(2, 2); + layerQuad.inflate(0.5); + EXPECT_TRUE(layerQuad.floatQuad() == quad); +} + +} // namespace diff --git a/cc/CCLayerSorter.cpp b/cc/CCLayerSorter.cpp new file mode 100644 index 0000000..bf2818b --- /dev/null +++ b/cc/CCLayerSorter.cpp @@ -0,0 +1,422 @@ +// 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 "config.h" + +#include "CCLayerSorter.h" + +#include "CCMathUtil.h" +#include "CCRenderSurface.h" +#include <limits.h> +#include <public/WebTransformationMatrix.h> +#include <wtf/Deque.h> + +using namespace std; +using WebKit::WebTransformationMatrix; + +#define LOG_CHANNEL_PREFIX Log +#define SHOW_DEBUG_LOG 0 + +#if !defined( NDEBUG ) +#if SHOW_DEBUG_LOG +static WTFLogChannel LogCCLayerSorter = { 0x00000000, "", WTFLogChannelOn }; +#else +static WTFLogChannel LogCCLayerSorter = { 0x00000000, "", WTFLogChannelOff }; +#endif +#endif + +namespace WebCore { + +inline static float perpProduct(const FloatSize& u, const FloatSize& v) +{ + return u.width() * v.height() - u.height() * v.width(); +} + +// Tests if two edges defined by their endpoints (a,b) and (c,d) intersect. Returns true and the +// point of intersection if they do and false otherwise. +static bool edgeEdgeTest(const FloatPoint& a, const FloatPoint& b, const FloatPoint& c, const FloatPoint& d, FloatPoint& r) +{ + FloatSize u = b - a; + FloatSize v = d - c; + FloatSize w = a - c; + + float denom = perpProduct(u, v); + + // If denom == 0 then the edges are parallel. While they could be overlapping + // we don't bother to check here as the we'll find their intersections from the + // corner to quad tests. + if (!denom) + return false; + + float s = perpProduct(v, w) / denom; + if (s < 0 || s > 1) + return false; + + float t = perpProduct(u, w) / denom; + if (t < 0 || t > 1) + return false; + + u.scale(s); + r = a + u; + return true; +} + +// Checks whether layer "a" draws on top of layer "b". The weight value returned is an indication of +// the maximum z-depth difference between the layers or zero if the layers are found to be intesecting +// (some features are in front and some are behind). +CCLayerSorter::ABCompareResult CCLayerSorter::checkOverlap(LayerShape* a, LayerShape* b, float zThreshold, float& weight) +{ + weight = 0; + + // Early out if the projected bounds don't overlap. + if (!a->projectedBounds.intersects(b->projectedBounds)) + return None; + + FloatPoint aPoints[4] = {a->projectedQuad.p1(), a->projectedQuad.p2(), a->projectedQuad.p3(), a->projectedQuad.p4() }; + FloatPoint bPoints[4] = {b->projectedQuad.p1(), b->projectedQuad.p2(), b->projectedQuad.p3(), b->projectedQuad.p4() }; + + // Make a list of points that inside both layer quad projections. + Vector<FloatPoint> overlapPoints; + + // Check all four corners of one layer against the other layer's quad. + for (int i = 0; i < 4; ++i) { + if (a->projectedQuad.containsPoint(bPoints[i])) + overlapPoints.append(bPoints[i]); + if (b->projectedQuad.containsPoint(aPoints[i])) + overlapPoints.append(aPoints[i]); + } + + // Check all the edges of one layer for intersection with the other layer's edges. + FloatPoint r; + for (int ea = 0; ea < 4; ++ea) + for (int eb = 0; eb < 4; ++eb) + if (edgeEdgeTest(aPoints[ea], aPoints[(ea + 1) % 4], + bPoints[eb], bPoints[(eb + 1) % 4], + r)) + overlapPoints.append(r); + + if (!overlapPoints.size()) + return None; + + // Check the corresponding layer depth value for all overlap points to determine + // which layer is in front. + float maxPositive = 0; + float maxNegative = 0; + for (unsigned o = 0; o < overlapPoints.size(); o++) { + float za = a->layerZFromProjectedPoint(overlapPoints[o]); + float zb = b->layerZFromProjectedPoint(overlapPoints[o]); + + float diff = za - zb; + if (diff > maxPositive) + maxPositive = diff; + if (diff < maxNegative) + maxNegative = diff; + } + + float maxDiff = (fabsf(maxPositive) > fabsf(maxNegative) ? maxPositive : maxNegative); + + // If the results are inconsistent (and the z difference substantial to rule out + // numerical errors) then the layers are intersecting. We will still return an + // order based on the maximum depth difference but with an edge weight of zero + // these layers will get priority if a graph cycle is present and needs to be broken. + if (maxPositive > zThreshold && maxNegative < -zThreshold) + weight = 0; + else + weight = fabsf(maxDiff); + + // Maintain relative order if the layers have the same depth at all intersection points. + if (maxDiff <= 0) + return ABeforeB; + + return BBeforeA; +} + +CCLayerSorter::LayerShape::LayerShape(float width, float height, const WebTransformationMatrix& drawTransform) +{ + FloatQuad layerQuad(FloatRect(0, 0, width, height)); + + // Compute the projection of the layer quad onto the z = 0 plane. + + FloatPoint clippedQuad[8]; + int numVerticesInClippedQuad; + CCMathUtil::mapClippedQuad(drawTransform, layerQuad, clippedQuad, numVerticesInClippedQuad); + + if (numVerticesInClippedQuad < 3) { + projectedBounds = FloatRect(); + return; + } + + projectedBounds = CCMathUtil::computeEnclosingRectOfVertices(clippedQuad, numVerticesInClippedQuad); + + // NOTE: it will require very significant refactoring and overhead to deal with + // generalized polygons or multiple quads per layer here. For the sake of layer + // sorting it is equally correct to take a subsection of the polygon that can be made + // into a quad. This will only be incorrect in the case of intersecting layers, which + // are not supported yet anyway. + projectedQuad.setP1(clippedQuad[0]); + projectedQuad.setP2(clippedQuad[1]); + projectedQuad.setP3(clippedQuad[2]); + if (numVerticesInClippedQuad >= 4) + projectedQuad.setP4(clippedQuad[3]); + else + projectedQuad.setP4(clippedQuad[2]); // this will be a degenerate quad that is actually a triangle. + + // Compute the normal of the layer's plane. + bool clipped = false; + FloatPoint3D c1 = CCMathUtil::mapPoint(drawTransform, FloatPoint3D(0, 0, 0), clipped); + FloatPoint3D c2 = CCMathUtil::mapPoint(drawTransform, FloatPoint3D(0, 1, 0), clipped); + FloatPoint3D c3 = CCMathUtil::mapPoint(drawTransform, FloatPoint3D(1, 0, 0), clipped); + // FIXME: Deal with clipping. + FloatPoint3D c12 = c2 - c1; + FloatPoint3D c13 = c3 - c1; + layerNormal = c13.cross(c12); + + transformOrigin = c1; +} + +// Returns the Z coordinate of a point on the layer that projects +// to point p which lies on the z = 0 plane. It does it by computing the +// intersection of a line starting from p along the Z axis and the plane +// of the layer. +float CCLayerSorter::LayerShape::layerZFromProjectedPoint(const FloatPoint& p) const +{ + const FloatPoint3D zAxis(0, 0, 1); + FloatPoint3D w = FloatPoint3D(p) - transformOrigin; + + float d = layerNormal.dot(zAxis); + float n = -layerNormal.dot(w); + + // Check if layer is parallel to the z = 0 axis which will make it + // invisible and hence returning zero is fine. + if (!d) + return 0; + + // The intersection point would be given by: + // p + (n / d) * u but since we are only interested in the + // z coordinate and p's z coord is zero, all we need is the value of n/d. + return n / d; +} + +void CCLayerSorter::createGraphNodes(LayerList::iterator first, LayerList::iterator last) +{ +#if !defined( NDEBUG ) + LOG(CCLayerSorter, "Creating graph nodes:\n"); +#endif + float minZ = FLT_MAX; + float maxZ = -FLT_MAX; + for (LayerList::const_iterator it = first; it < last; it++) { + m_nodes.append(GraphNode(*it)); + GraphNode& node = m_nodes.at(m_nodes.size() - 1); + CCRenderSurface* renderSurface = node.layer->renderSurface(); + if (!node.layer->drawsContent() && !renderSurface) + continue; + +#if !defined( NDEBUG ) + LOG(CCLayerSorter, "Layer %d (%d x %d)\n", node.layer->id(), node.layer->bounds().width(), node.layer->bounds().height()); +#endif + + WebTransformationMatrix drawTransform; + float layerWidth, layerHeight; + if (renderSurface) { + drawTransform = renderSurface->drawTransform(); + layerWidth = renderSurface->contentRect().width(); + layerHeight = renderSurface->contentRect().height(); + } else { + drawTransform = node.layer->drawTransform(); + layerWidth = node.layer->contentBounds().width(); + layerHeight = node.layer->contentBounds().height(); + } + + node.shape = LayerShape(layerWidth, layerHeight, drawTransform); + + maxZ = max(maxZ, node.shape.transformOrigin.z()); + minZ = min(minZ, node.shape.transformOrigin.z()); + } + + m_zRange = fabsf(maxZ - minZ); +} + +void CCLayerSorter::createGraphEdges() +{ +#if !defined( NDEBUG ) + LOG(CCLayerSorter, "Edges:\n"); +#endif + // Fraction of the total zRange below which z differences + // are not considered reliable. + const float zThresholdFactor = 0.01; + float zThreshold = m_zRange * zThresholdFactor; + + for (unsigned na = 0; na < m_nodes.size(); na++) { + GraphNode& nodeA = m_nodes[na]; + if (!nodeA.layer->drawsContent() && !nodeA.layer->renderSurface()) + continue; + for (unsigned nb = na + 1; nb < m_nodes.size(); nb++) { + GraphNode& nodeB = m_nodes[nb]; + if (!nodeB.layer->drawsContent() && !nodeB.layer->renderSurface()) + continue; + float weight = 0; + ABCompareResult overlapResult = checkOverlap(&nodeA.shape, &nodeB.shape, zThreshold, weight); + GraphNode* startNode = 0; + GraphNode* endNode = 0; + if (overlapResult == ABeforeB) { + startNode = &nodeA; + endNode = &nodeB; + } else if (overlapResult == BBeforeA) { + startNode = &nodeB; + endNode = &nodeA; + } + + if (startNode) { +#if !defined( NDEBUG ) + LOG(CCLayerSorter, "%d -> %d\n", startNode->layer->id(), endNode->layer->id()); +#endif + m_edges.append(GraphEdge(startNode, endNode, weight)); + } + } + } + + for (unsigned i = 0; i < m_edges.size(); i++) { + GraphEdge& edge = m_edges[i]; + m_activeEdges.add(&edge, &edge); + edge.from->outgoing.append(&edge); + edge.to->incoming.append(&edge); + edge.to->incomingEdgeWeight += edge.weight; + } +} + +// Finds and removes an edge from the list by doing a swap with the +// last element of the list. +void CCLayerSorter::removeEdgeFromList(GraphEdge* edge, Vector<GraphEdge*>& list) +{ + size_t edgeIndex = list.find(edge); + ASSERT(edgeIndex != notFound); + if (list.size() == 1) { + ASSERT(!edgeIndex); + list.clear(); + return; + } + if (edgeIndex != list.size() - 1) + list[edgeIndex] = list[list.size() - 1]; + + list.removeLast(); +} + +// Sorts the given list of layers such that they can be painted in a back-to-front +// order. Sorting produces correct results for non-intersecting layers that don't have +// cyclical order dependencies. Cycles and intersections are broken (somewhat) aribtrarily. +// Sorting of layers is done via a topological sort of a directed graph whose nodes are +// the layers themselves. An edge from node A to node B signifies that layer A needs to +// be drawn before layer B. If A and B have no dependency between each other, then we +// preserve the ordering of those layers as they were in the original list. +// +// The draw order between two layers is determined by projecting the two triangles making +// up each layer quad to the Z = 0 plane, finding points of intersection between the triangles +// and backprojecting those points to the plane of the layer to determine the corresponding Z +// coordinate. The layer with the lower Z coordinate (farther from the eye) needs to be rendered +// first. +// +// If the layer projections don't intersect, then no edges (dependencies) are created +// between them in the graph. HOWEVER, in this case we still need to preserve the ordering +// of the original list of layers, since that list should already have proper z-index +// ordering of layers. +// +void CCLayerSorter::sort(LayerList::iterator first, LayerList::iterator last) +{ +#if !defined( NDEBUG ) + LOG(CCLayerSorter, "Sorting start ----\n"); +#endif + createGraphNodes(first, last); + + createGraphEdges(); + + Vector<GraphNode*> sortedList; + Deque<GraphNode*> noIncomingEdgeNodeList; + + // Find all the nodes that don't have incoming edges. + for (NodeList::iterator la = m_nodes.begin(); la < m_nodes.end(); la++) { + if (!la->incoming.size()) + noIncomingEdgeNodeList.append(la); + } + +#if !defined( NDEBUG ) + LOG(CCLayerSorter, "Sorted list: "); +#endif + while (m_activeEdges.size() || noIncomingEdgeNodeList.size()) { + while (noIncomingEdgeNodeList.size()) { + + // It is necessary to preserve the existing ordering of layers, when there are + // no explicit dependencies (because this existing ordering has correct + // z-index/layout ordering). To preserve this ordering, we process Nodes in + // the same order that they were added to the list. + GraphNode* fromNode = noIncomingEdgeNodeList.takeFirst(); + + // Add it to the final list. + sortedList.append(fromNode); + +#if !defined( NDEBUG ) + LOG(CCLayerSorter, "%d, ", fromNode->layer->id()); +#endif + + // Remove all its outgoing edges from the graph. + for (unsigned i = 0; i < fromNode->outgoing.size(); i++) { + GraphEdge* outgoingEdge = fromNode->outgoing[i]; + + m_activeEdges.remove(outgoingEdge); + removeEdgeFromList(outgoingEdge, outgoingEdge->to->incoming); + outgoingEdge->to->incomingEdgeWeight -= outgoingEdge->weight; + + if (!outgoingEdge->to->incoming.size()) + noIncomingEdgeNodeList.append(outgoingEdge->to); + } + fromNode->outgoing.clear(); + } + + if (!m_activeEdges.size()) + break; + + // If there are still active edges but the list of nodes without incoming edges + // is empty then we have run into a cycle. Break the cycle by finding the node + // with the smallest overall incoming edge weight and use it. This will favor + // nodes that have zero-weight incoming edges i.e. layers that are being + // occluded by a layer that intersects them. + float minIncomingEdgeWeight = FLT_MAX; + GraphNode* nextNode = 0; + for (unsigned i = 0; i < m_nodes.size(); i++) { + if (m_nodes[i].incoming.size() && m_nodes[i].incomingEdgeWeight < minIncomingEdgeWeight) { + minIncomingEdgeWeight = m_nodes[i].incomingEdgeWeight; + nextNode = &m_nodes[i]; + } + } + ASSERT(nextNode); + // Remove all its incoming edges. + for (unsigned e = 0; e < nextNode->incoming.size(); e++) { + GraphEdge* incomingEdge = nextNode->incoming[e]; + + m_activeEdges.remove(incomingEdge); + removeEdgeFromList(incomingEdge, incomingEdge->from->outgoing); + } + nextNode->incoming.clear(); + nextNode->incomingEdgeWeight = 0; + noIncomingEdgeNodeList.append(nextNode); +#if !defined( NDEBUG ) + LOG(CCLayerSorter, "Breaking cycle by cleaning up incoming edges from %d (weight = %f)\n", nextNode->layer->id(), minIncomingEdgeWeight); +#endif + } + + // Note: The original elements of the list are in no danger of having their ref count go to zero + // here as they are all nodes of the layer hierarchy and are kept alive by their parent nodes. + int count = 0; + for (LayerList::iterator it = first; it < last; it++) + *it = sortedList[count++]->layer; + +#if !defined( NDEBUG ) + LOG(CCLayerSorter, "Sorting end ----\n"); +#endif + + m_nodes.clear(); + m_edges.clear(); + m_activeEdges.clear(); +} + +} diff --git a/cc/CCLayerSorter.h b/cc/CCLayerSorter.h new file mode 100644 index 0000000..0fb64a6 --- /dev/null +++ b/cc/CCLayerSorter.h @@ -0,0 +1,88 @@ +// 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. + +#ifndef CCLayerSorter_h +#define CCLayerSorter_h + +#include "CCLayerImpl.h" +#include "FloatPoint3D.h" +#include "FloatQuad.h" +#include "FloatRect.h" +#include <wtf/HashMap.h> +#include <wtf/Noncopyable.h> +#include <wtf/Vector.h> + +namespace WebKit { +class WebTransformationMatrix; +} + +namespace WebCore { + +class CCLayerSorter { + WTF_MAKE_NONCOPYABLE(CCLayerSorter); +public: + CCLayerSorter() : m_zRange(0) { } + + typedef Vector<CCLayerImpl*> LayerList; + + void sort(LayerList::iterator first, LayerList::iterator last); + + // Holds various useful properties derived from a layer's 3D outline. + struct LayerShape { + LayerShape() { } + LayerShape(float width, float height, const WebKit::WebTransformationMatrix& drawTransform); + + float layerZFromProjectedPoint(const FloatPoint&) const; + + FloatPoint3D layerNormal; + FloatPoint3D transformOrigin; + FloatQuad projectedQuad; + FloatRect projectedBounds; + }; + + enum ABCompareResult { + ABeforeB, + BBeforeA, + None + }; + + static ABCompareResult checkOverlap(LayerShape*, LayerShape*, float zThreshold, float& weight); + +private: + struct GraphEdge; + + struct GraphNode { + explicit GraphNode(CCLayerImpl* cclayer) : layer(cclayer), incomingEdgeWeight(0) { } + + CCLayerImpl* layer; + LayerShape shape; + Vector<GraphEdge*> incoming; + Vector<GraphEdge*> outgoing; + float incomingEdgeWeight; + }; + + struct GraphEdge { + GraphEdge(GraphNode* fromNode, GraphNode* toNode, float weight) : from(fromNode), to(toNode), weight(weight) { }; + + GraphNode* from; + GraphNode* to; + float weight; + }; + + typedef Vector<GraphNode> NodeList; + typedef Vector<GraphEdge> EdgeList; + NodeList m_nodes; + EdgeList m_edges; + float m_zRange; + + typedef HashMap<GraphEdge*, GraphEdge*> EdgeMap; + EdgeMap m_activeEdges; + + void createGraphNodes(LayerList::iterator first, LayerList::iterator last); + void createGraphEdges(); + void removeEdgeFromList(GraphEdge*, Vector<GraphEdge*>&); +}; + +} +#endif diff --git a/cc/CCLayerSorterTest.cpp b/cc/CCLayerSorterTest.cpp new file mode 100644 index 0000000..672f652 --- /dev/null +++ b/cc/CCLayerSorterTest.cpp @@ -0,0 +1,267 @@ +// 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 "config.h" + +#include "CCLayerSorter.h" + +#include "CCLayerImpl.h" +#include "CCMathUtil.h" +#include "CCSingleThreadProxy.h" +#include <gtest/gtest.h> +#include <public/WebTransformationMatrix.h> + +using namespace WebCore; +using WebKit::WebTransformationMatrix; + +namespace { + +// Note: In the following overlap tests, the "camera" is looking down the negative Z axis, +// meaning that layers with smaller z values (more negative) are further from the camera +// and therefore must be drawn before layers with higher z values. + +TEST(CCLayerSorterTest, BasicOverlap) +{ + CCLayerSorter::ABCompareResult overlapResult; + const float zThreshold = 0.1f; + float weight = 0; + + // Trivial test, with one layer directly obscuring the other. + WebTransformationMatrix neg4Translate; + neg4Translate.translate3d(0, 0, -4); + CCLayerSorter::LayerShape front(2, 2, neg4Translate); + + WebTransformationMatrix neg5Translate; + neg5Translate.translate3d(0, 0, -5); + CCLayerSorter::LayerShape back(2, 2, neg5Translate); + + overlapResult = CCLayerSorter::checkOverlap(&front, &back, zThreshold, weight); + EXPECT_EQ(CCLayerSorter::BBeforeA, overlapResult); + EXPECT_EQ(1, weight); + + overlapResult = CCLayerSorter::checkOverlap(&back, &front, zThreshold, weight); + EXPECT_EQ(CCLayerSorter::ABeforeB, overlapResult); + EXPECT_EQ(1, weight); + + // One layer translated off to the right. No overlap should be detected. + WebTransformationMatrix rightTranslate; + rightTranslate.translate3d(10, 0, -5); + CCLayerSorter::LayerShape backRight(2, 2, rightTranslate); + overlapResult = CCLayerSorter::checkOverlap(&front, &backRight, zThreshold, weight); + EXPECT_EQ(CCLayerSorter::None, overlapResult); + + // When comparing a layer with itself, z difference is always 0. + overlapResult = CCLayerSorter::checkOverlap(&front, &front, zThreshold, weight); + EXPECT_EQ(0, weight); +} + +TEST(CCLayerSorterTest, RightAngleOverlap) +{ + CCLayerSorter::ABCompareResult overlapResult; + const float zThreshold = 0.1f; + float weight = 0; + + WebTransformationMatrix perspectiveMatrix; + perspectiveMatrix.applyPerspective(1000); + + // Two layers forming a right angle with a perspective viewing transform. + WebTransformationMatrix leftFaceMatrix; + leftFaceMatrix.rotate3d(0, 1, 0, -90); + leftFaceMatrix.translateRight3d(-1, 0, -5); + leftFaceMatrix.translate(-1, -1); + CCLayerSorter::LayerShape leftFace(2, 2, perspectiveMatrix * leftFaceMatrix); + WebTransformationMatrix frontFaceMatrix; + frontFaceMatrix.translate3d(0, 0, -4); + frontFaceMatrix.translate(-1, -1); + CCLayerSorter::LayerShape frontFace(2, 2, perspectiveMatrix * frontFaceMatrix); + + overlapResult = CCLayerSorter::checkOverlap(&frontFace, &leftFace, zThreshold, weight); + EXPECT_EQ(CCLayerSorter::BBeforeA, overlapResult); +} + +TEST(CCLayerSorterTest, IntersectingLayerOverlap) +{ + CCLayerSorter::ABCompareResult overlapResult; + const float zThreshold = 0.1f; + float weight = 0; + + WebTransformationMatrix perspectiveMatrix; + perspectiveMatrix.applyPerspective(1000); + + // Intersecting layers. An explicit order will be returned based on relative z + // values at the overlapping features but the weight returned should be zero. + WebTransformationMatrix frontFaceMatrix; + frontFaceMatrix.translate3d(0, 0, -4); + frontFaceMatrix.translate(-1, -1); + CCLayerSorter::LayerShape frontFace(2, 2, perspectiveMatrix * frontFaceMatrix); + + WebTransformationMatrix throughMatrix; + throughMatrix.rotate3d(0, 1, 0, 45); + throughMatrix.translateRight3d(0, 0, -4); + throughMatrix.translate(-1, -1); + CCLayerSorter::LayerShape rotatedFace(2, 2, perspectiveMatrix * throughMatrix); + overlapResult = CCLayerSorter::checkOverlap(&frontFace, &rotatedFace, zThreshold, weight); + EXPECT_NE(CCLayerSorter::None, overlapResult); + EXPECT_EQ(0, weight); +} + +TEST(CCLayerSorterTest, LayersAtAngleOverlap) +{ + CCLayerSorter::ABCompareResult overlapResult; + const float zThreshold = 0.1f; + float weight = 0; + + // Trickier test with layers at an angle. + // + // -x . . . . 0 . . . . +x + // -z / + // : /----B---- + // 0 C + // : ----A----/ + // +z / + // + // C is in front of A and behind B (not what you'd expect by comparing centers). + // A and B don't overlap, so they're incomparable. + + WebTransformationMatrix transformA; + transformA.translate3d(-6, 0, 1); + transformA.translate(-4, -10); + CCLayerSorter::LayerShape layerA(8, 20, transformA); + + WebTransformationMatrix transformB; + transformB.translate3d(6, 0, -1); + transformB.translate(-4, -10); + CCLayerSorter::LayerShape layerB(8, 20, transformB); + + WebTransformationMatrix transformC; + transformC.rotate3d(0, 1, 0, 40); + transformC.translate(-4, -10); + CCLayerSorter::LayerShape layerC(8, 20, transformC); + + overlapResult = CCLayerSorter::checkOverlap(&layerA, &layerC, zThreshold, weight); + EXPECT_EQ(CCLayerSorter::ABeforeB, overlapResult); + overlapResult = CCLayerSorter::checkOverlap(&layerC, &layerB, zThreshold, weight); + EXPECT_EQ(CCLayerSorter::ABeforeB, overlapResult); + overlapResult = CCLayerSorter::checkOverlap(&layerA, &layerB, zThreshold, weight); + EXPECT_EQ(CCLayerSorter::None, overlapResult); +} + +TEST(CCLayerSorterTest, LayersUnderPathologicalPerspectiveTransform) +{ + CCLayerSorter::ABCompareResult overlapResult; + const float zThreshold = 0.1f; + float weight = 0; + + // On perspective projection, if w becomes negative, the re-projected point will be + // invalid and un-usable. Correct code needs to clip away portions of the geometry + // where w < 0. If the code uses the invalid value, it will think that a layer has + // different bounds than it really does, which can cause things to sort incorrectly. + + WebTransformationMatrix perspectiveMatrix; + perspectiveMatrix.applyPerspective(1); + + WebTransformationMatrix transformA; + transformA.translate3d(-15, 0, -2); + transformA.translate(-5, -5); + CCLayerSorter::LayerShape layerA(10, 10, perspectiveMatrix * transformA); + + // With this sequence of transforms, when layer B is correctly clipped, it will be + // visible on the left half of the projection plane, in front of layerA. When it is + // not clipped, its bounds will actually incorrectly appear much smaller and the + // correct sorting dependency will not be found. + WebTransformationMatrix transformB; + transformB.translate3d(0, 0, 0.7); + transformB.rotate3d(0, 45, 0); + transformB.translate(-5, -5); + CCLayerSorter::LayerShape layerB(10, 10, perspectiveMatrix * transformB); + + // Sanity check that the test case actually covers the intended scenario, where part + // of layer B go behind the w = 0 plane. + FloatQuad testQuad = FloatQuad(FloatRect(FloatPoint(-0.5, -0.5), FloatSize(1, 1))); + bool clipped = false; + CCMathUtil::mapQuad(perspectiveMatrix * transformB, testQuad, clipped); + ASSERT_TRUE(clipped); + + overlapResult = CCLayerSorter::checkOverlap(&layerA, &layerB, zThreshold, weight); + EXPECT_EQ(CCLayerSorter::ABeforeB, overlapResult); +} + +TEST(CCLayerSorterTest, verifyExistingOrderingPreservedWhenNoZDiff) +{ + DebugScopedSetImplThread thisScopeIsOnImplThread; + + // If there is no reason to re-sort the layers (i.e. no 3d z difference), then the + // existing ordering provided on input should be retained. This test covers the fix in + // https://bugs.webkit.org/show_bug.cgi?id=75046. Before this fix, ordering was + // accidentally reversed, causing bugs in z-index ordering on websites when + // preserves3D triggered the CCLayerSorter. + + // Input list of layers: [1, 2, 3, 4, 5]. + // Expected output: [3, 4, 1, 2, 5]. + // - 1, 2, and 5 do not have a 3d z difference, and therefore their relative ordering should be retained. + // - 3 and 4 do not have a 3d z difference, and therefore their relative ordering should be retained. + // - 3 and 4 should be re-sorted so they are in front of 1, 2, and 5. + + OwnPtr<CCLayerImpl> layer1 = CCLayerImpl::create(1); + OwnPtr<CCLayerImpl> layer2 = CCLayerImpl::create(2); + OwnPtr<CCLayerImpl> layer3 = CCLayerImpl::create(3); + OwnPtr<CCLayerImpl> layer4 = CCLayerImpl::create(4); + OwnPtr<CCLayerImpl> layer5 = CCLayerImpl::create(5); + + WebTransformationMatrix BehindMatrix; + BehindMatrix.translate3d(0, 0, 2); + WebTransformationMatrix FrontMatrix; + FrontMatrix.translate3d(0, 0, 1); + + layer1->setBounds(IntSize(10, 10)); + layer1->setContentBounds(IntSize(10, 10)); + layer1->setDrawTransform(BehindMatrix); + layer1->setDrawsContent(true); + + layer2->setBounds(IntSize(20, 20)); + layer2->setContentBounds(IntSize(20, 20)); + layer2->setDrawTransform(BehindMatrix); + layer2->setDrawsContent(true); + + layer3->setBounds(IntSize(30, 30)); + layer3->setContentBounds(IntSize(30, 30)); + layer3->setDrawTransform(FrontMatrix); + layer3->setDrawsContent(true); + + layer4->setBounds(IntSize(40, 40)); + layer4->setContentBounds(IntSize(40, 40)); + layer4->setDrawTransform(FrontMatrix); + layer4->setDrawsContent(true); + + layer5->setBounds(IntSize(50, 50)); + layer5->setContentBounds(IntSize(50, 50)); + layer5->setDrawTransform(BehindMatrix); + layer5->setDrawsContent(true); + + Vector<CCLayerImpl*> layerList; + layerList.append(layer1.get()); + layerList.append(layer2.get()); + layerList.append(layer3.get()); + layerList.append(layer4.get()); + layerList.append(layer5.get()); + + ASSERT_EQ(static_cast<size_t>(5), layerList.size()); + EXPECT_EQ(1, layerList[0]->id()); + EXPECT_EQ(2, layerList[1]->id()); + EXPECT_EQ(3, layerList[2]->id()); + EXPECT_EQ(4, layerList[3]->id()); + EXPECT_EQ(5, layerList[4]->id()); + + CCLayerSorter layerSorter; + layerSorter.sort(layerList.begin(), layerList.end()); + + ASSERT_EQ(static_cast<size_t>(5), layerList.size()); + EXPECT_EQ(3, layerList[0]->id()); + EXPECT_EQ(4, layerList[1]->id()); + EXPECT_EQ(1, layerList[2]->id()); + EXPECT_EQ(2, layerList[3]->id()); + EXPECT_EQ(5, layerList[4]->id()); +} + +} // namespace diff --git a/cc/CCLayerTilingData.cpp b/cc/CCLayerTilingData.cpp new file mode 100644 index 0000000..04ccda5 --- /dev/null +++ b/cc/CCLayerTilingData.cpp @@ -0,0 +1,149 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCLayerTilingData.h" + +using namespace std; + +namespace WebCore { + +PassOwnPtr<CCLayerTilingData> CCLayerTilingData::create(const IntSize& tileSize, BorderTexelOption border) +{ + return adoptPtr(new CCLayerTilingData(tileSize, border)); +} + +CCLayerTilingData::CCLayerTilingData(const IntSize& tileSize, BorderTexelOption border) + : m_tilingData(tileSize, IntSize(), border == HasBorderTexels) +{ + setTileSize(tileSize); +} + +void CCLayerTilingData::setTileSize(const IntSize& size) +{ + if (tileSize() == size) + return; + + reset(); + + m_tilingData.setMaxTextureSize(size); +} + +const IntSize& CCLayerTilingData::tileSize() const +{ + return m_tilingData.maxTextureSize(); +} + +void CCLayerTilingData::setBorderTexelOption(BorderTexelOption borderTexelOption) +{ + bool borderTexels = borderTexelOption == HasBorderTexels; + if (hasBorderTexels() == borderTexels) + return; + + reset(); + m_tilingData.setHasBorderTexels(borderTexels); +} + +const CCLayerTilingData& CCLayerTilingData::operator=(const CCLayerTilingData& tiler) +{ + m_tilingData = tiler.m_tilingData; + + return *this; +} + +void CCLayerTilingData::addTile(PassOwnPtr<Tile> tile, int i, int j) +{ + ASSERT(!tileAt(i, j)); + tile->moveTo(i, j); + m_tiles.add(make_pair(i, j), tile); +} + +PassOwnPtr<CCLayerTilingData::Tile> CCLayerTilingData::takeTile(int i, int j) +{ + return m_tiles.take(make_pair(i, j)); +} + +CCLayerTilingData::Tile* CCLayerTilingData::tileAt(int i, int j) const +{ + return m_tiles.get(make_pair(i, j)); +} + +void CCLayerTilingData::reset() +{ + m_tiles.clear(); +} + +void CCLayerTilingData::contentRectToTileIndices(const IntRect& contentRect, int& left, int& top, int& right, int& bottom) const +{ + // An empty rect doesn't result in an empty set of tiles, so don't pass an empty rect. + // FIXME: Possibly we should fill a vector of tiles instead, + // since the normal use of this function is to enumerate some tiles. + ASSERT(!contentRect.isEmpty()); + + left = m_tilingData.tileXIndexFromSrcCoord(contentRect.x()); + top = m_tilingData.tileYIndexFromSrcCoord(contentRect.y()); + right = m_tilingData.tileXIndexFromSrcCoord(contentRect.maxX() - 1); + bottom = m_tilingData.tileYIndexFromSrcCoord(contentRect.maxY() - 1); +} + +IntRect CCLayerTilingData::tileRect(const Tile* tile) const +{ + IntRect tileRect = m_tilingData.tileBoundsWithBorder(tile->i(), tile->j()); + tileRect.setSize(tileSize()); + return tileRect; +} + +Region CCLayerTilingData::opaqueRegionInContentRect(const IntRect& contentRect) const +{ + if (contentRect.isEmpty()) + return Region(); + + Region opaqueRegion; + int left, top, right, bottom; + contentRectToTileIndices(contentRect, left, top, right, bottom); + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + Tile* tile = tileAt(i, j); + if (!tile) + continue; + + IntRect tileOpaqueRect = intersection(contentRect, tile->opaqueRect()); + opaqueRegion.unite(tileOpaqueRect); + } + } + return opaqueRegion; +} + +void CCLayerTilingData::setBounds(const IntSize& size) +{ + m_tilingData.setTotalSize(size); + if (size.isEmpty()) { + m_tiles.clear(); + return; + } + + // Any tiles completely outside our new bounds are invalid and should be dropped. + int left, top, right, bottom; + contentRectToTileIndices(IntRect(IntPoint(), size), left, top, right, bottom); + Vector<TileMapKey> invalidTileKeys; + for (TileMap::const_iterator it = m_tiles.begin(); it != m_tiles.end(); ++it) { + if (it->first.first > right || it->first.second > bottom) + invalidTileKeys.append(it->first); + } + for (size_t i = 0; i < invalidTileKeys.size(); ++i) + m_tiles.remove(invalidTileKeys[i]); +} + +IntSize CCLayerTilingData::bounds() const +{ + return m_tilingData.totalSize(); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCLayerTilingData.h b/cc/CCLayerTilingData.h new file mode 100644 index 0000000..391639a --- /dev/null +++ b/cc/CCLayerTilingData.h @@ -0,0 +1,98 @@ +// 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. + + +#ifndef CCLayerTilingData_h +#define CCLayerTilingData_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "IntRect.h" +#include "Region.h" +#include "TilingData.h" +#include <wtf/HashMap.h> +#include <wtf/HashTraits.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCLayerTilingData { +public: + enum BorderTexelOption { HasBorderTexels, NoBorderTexels }; + + static PassOwnPtr<CCLayerTilingData> create(const IntSize& tileSize, BorderTexelOption); + + bool hasEmptyBounds() const { return m_tilingData.hasEmptyBounds(); } + int numTilesX() const { return m_tilingData.numTilesX(); } + int numTilesY() const { return m_tilingData.numTilesY(); } + IntRect tileBounds(int i, int j) const { return m_tilingData.tileBounds(i, j); } + IntPoint textureOffset(int xIndex, int yIndex) const { return m_tilingData.textureOffset(xIndex, yIndex); } + + // Change the tile size. This may invalidate all the existing tiles. + void setTileSize(const IntSize&); + const IntSize& tileSize() const; + // Change the border texel setting. This may invalidate all existing tiles. + void setBorderTexelOption(BorderTexelOption); + bool hasBorderTexels() const { return m_tilingData.borderTexels(); } + + bool isEmpty() const { return hasEmptyBounds() || !tiles().size(); } + + const CCLayerTilingData& operator=(const CCLayerTilingData&); + + class Tile { + WTF_MAKE_NONCOPYABLE(Tile); + public: + Tile() : m_i(-1), m_j(-1) { } + virtual ~Tile() { } + + int i() const { return m_i; } + int j() const { return m_j; } + void moveTo(int i, int j) { m_i = i; m_j = j; } + + const IntRect& opaqueRect() const { return m_opaqueRect; } + void setOpaqueRect(const IntRect& opaqueRect) { m_opaqueRect = opaqueRect; } + private: + int m_i; + int m_j; + IntRect m_opaqueRect; + }; + // Default hash key traits for integers disallow 0 and -1 as a key, so + // use a custom hash trait which disallows -1 and -2 instead. + typedef std::pair<int, int> TileMapKey; + struct TileMapKeyTraits : HashTraits<TileMapKey> { + static const bool emptyValueIsZero = false; + static const bool needsDestruction = false; + static TileMapKey emptyValue() { return std::make_pair(-1, -1); } + static void constructDeletedValue(TileMapKey& slot) { slot = std::make_pair(-2, -2); } + static bool isDeletedValue(TileMapKey value) { return value.first == -2 && value.second == -2; } + }; + typedef HashMap<TileMapKey, OwnPtr<Tile>, DefaultHash<TileMapKey>::Hash, TileMapKeyTraits> TileMap; + + void addTile(PassOwnPtr<Tile>, int, int); + PassOwnPtr<Tile> takeTile(int, int); + Tile* tileAt(int, int) const; + const TileMap& tiles() const { return m_tiles; } + + void setBounds(const IntSize&); + IntSize bounds() const; + + void contentRectToTileIndices(const IntRect&, int &left, int &top, int &right, int &bottom) const; + IntRect tileRect(const Tile*) const; + + Region opaqueRegionInContentRect(const IntRect&) const; + + void reset(); + +protected: + CCLayerTilingData(const IntSize& tileSize, BorderTexelOption); + + TileMap m_tiles; + TilingData m_tilingData; +}; + +} + +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/CCLayerTreeHost.cpp b/cc/CCLayerTreeHost.cpp new file mode 100644 index 0000000..aa8d441 --- /dev/null +++ b/cc/CCLayerTreeHost.cpp @@ -0,0 +1,752 @@ +// 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 "config.h" + +#include "CCLayerTreeHost.h" + +#include "CCFontAtlas.h" +#include "CCGraphicsContext.h" +#include "CCHeadsUpDisplayLayerImpl.h" +#include "CCLayerAnimationController.h" +#include "CCLayerIterator.h" +#include "CCLayerTreeHostCommon.h" +#include "CCLayerTreeHostImpl.h" +#include "CCOcclusionTracker.h" +#include "CCOverdrawMetrics.h" +#include "CCSettings.h" +#include "CCSingleThreadProxy.h" +#include "CCThreadProxy.h" +#include "HeadsUpDisplayLayerChromium.h" +#include "LayerChromium.h" +#include "Region.h" +#include "TraceEvent.h" +#include "TreeSynchronizer.h" + +using namespace std; +using WebKit::WebTransformationMatrix; + +namespace { +static int numLayerTreeInstances; +} + +namespace WebCore { + +bool CCLayerTreeHost::s_needsFilterContext = false; + +bool CCLayerTreeHost::anyLayerTreeHostInstanceExists() +{ + return numLayerTreeInstances > 0; +} + +PassOwnPtr<CCLayerTreeHost> CCLayerTreeHost::create(CCLayerTreeHostClient* client, const CCLayerTreeSettings& settings) +{ + OwnPtr<CCLayerTreeHost> layerTreeHost = adoptPtr(new CCLayerTreeHost(client, settings)); + if (!layerTreeHost->initialize()) + return nullptr; + return layerTreeHost.release(); +} + +CCLayerTreeHost::CCLayerTreeHost(CCLayerTreeHostClient* client, const CCLayerTreeSettings& settings) + : m_compositorIdentifier(-1) + , m_animating(false) + , m_needsAnimateLayers(false) + , m_client(client) + , m_commitNumber(0) + , m_renderingStats() + , m_rendererInitialized(false) + , m_contextLost(false) + , m_numTimesRecreateShouldFail(0) + , m_numFailedRecreateAttempts(0) + , m_settings(settings) + , m_deviceScaleFactor(1) + , m_visible(true) + , m_pageScaleFactor(1) + , m_minPageScaleFactor(1) + , m_maxPageScaleFactor(1) + , m_triggerIdleUpdates(true) + , m_backgroundColor(SK_ColorWHITE) + , m_hasTransparentBackground(false) + , m_partialTextureUpdateRequests(0) +{ + ASSERT(CCProxy::isMainThread()); + numLayerTreeInstances++; +} + +bool CCLayerTreeHost::initialize() +{ + TRACE_EVENT0("cc", "CCLayerTreeHost::initialize"); + + if (CCProxy::hasImplThread()) + m_proxy = CCThreadProxy::create(this); + else + m_proxy = CCSingleThreadProxy::create(this); + m_proxy->start(); + + if (!m_proxy->initializeContext()) + return false; + + m_compositorIdentifier = m_proxy->compositorIdentifier(); + return true; +} + +CCLayerTreeHost::~CCLayerTreeHost() +{ + if (m_rootLayer) + m_rootLayer->setLayerTreeHost(0); + ASSERT(CCProxy::isMainThread()); + TRACE_EVENT0("cc", "CCLayerTreeHost::~CCLayerTreeHost"); + ASSERT(m_proxy); + m_proxy->stop(); + m_proxy.clear(); + numLayerTreeInstances--; + RateLimiterMap::iterator it = m_rateLimiters.begin(); + if (it != m_rateLimiters.end()) + it->second->stop(); +} + +void CCLayerTreeHost::setSurfaceReady() +{ + m_proxy->setSurfaceReady(); +} + +void CCLayerTreeHost::initializeRenderer() +{ + TRACE_EVENT0("cc", "CCLayerTreeHost::initializeRenderer"); + if (!m_proxy->initializeRenderer()) { + // Uh oh, better tell the client that we can't do anything with this context. + m_client->didRecreateOutputSurface(false); + return; + } + + // Update m_settings based on capabilities that we got back from the renderer. + m_settings.acceleratePainting = m_proxy->rendererCapabilities().usingAcceleratedPainting; + + // Update m_settings based on partial update capability. + m_settings.maxPartialTextureUpdates = min(m_settings.maxPartialTextureUpdates, m_proxy->maxPartialTextureUpdates()); + + m_contentsTextureManager = CCPrioritizedTextureManager::create(0, m_proxy->rendererCapabilities().maxTextureSize, CCRenderer::ContentPool); + m_surfaceMemoryPlaceholder = m_contentsTextureManager->createTexture(IntSize(), GraphicsContext3D::RGBA); + + m_rendererInitialized = true; + + m_settings.defaultTileSize = IntSize(min(m_settings.defaultTileSize.width(), m_proxy->rendererCapabilities().maxTextureSize), + min(m_settings.defaultTileSize.height(), m_proxy->rendererCapabilities().maxTextureSize)); + m_settings.maxUntiledLayerSize = IntSize(min(m_settings.maxUntiledLayerSize.width(), m_proxy->rendererCapabilities().maxTextureSize), + min(m_settings.maxUntiledLayerSize.height(), m_proxy->rendererCapabilities().maxTextureSize)); +} + +CCLayerTreeHost::RecreateResult CCLayerTreeHost::recreateContext() +{ + TRACE_EVENT0("cc", "CCLayerTreeHost::recreateContext"); + ASSERT(m_contextLost); + + bool recreated = false; + if (!m_numTimesRecreateShouldFail) + recreated = m_proxy->recreateContext(); + else + m_numTimesRecreateShouldFail--; + + if (recreated) { + m_client->didRecreateOutputSurface(true); + m_contextLost = false; + return RecreateSucceeded; + } + + // Tolerate a certain number of recreation failures to work around races + // in the context-lost machinery. + m_numFailedRecreateAttempts++; + if (m_numFailedRecreateAttempts < 5) { + // FIXME: The single thread does not self-schedule context + // recreation. So force another recreation attempt to happen by requesting + // another commit. + if (!CCProxy::hasImplThread()) + setNeedsCommit(); + return RecreateFailedButTryAgain; + } + + // We have tried too many times to recreate the context. Tell the host to fall + // back to software rendering. + m_client->didRecreateOutputSurface(false); + return RecreateFailedAndGaveUp; +} + +void CCLayerTreeHost::deleteContentsTexturesOnImplThread(CCResourceProvider* resourceProvider) +{ + ASSERT(CCProxy::isImplThread()); + if (m_rendererInitialized) + m_contentsTextureManager->clearAllMemory(resourceProvider); +} + +void CCLayerTreeHost::acquireLayerTextures() +{ + ASSERT(CCProxy::isMainThread()); + m_proxy->acquireLayerTextures(); +} + +void CCLayerTreeHost::updateAnimations(double monotonicFrameBeginTime) +{ + m_animating = true; + m_client->animate(monotonicFrameBeginTime); + animateLayers(monotonicFrameBeginTime); + m_animating = false; + + m_renderingStats.numAnimationFrames++; +} + +void CCLayerTreeHost::layout() +{ + m_client->layout(); +} + +void CCLayerTreeHost::beginCommitOnImplThread(CCLayerTreeHostImpl* hostImpl) +{ + ASSERT(CCProxy::isImplThread()); + TRACE_EVENT0("cc", "CCLayerTreeHost::commitTo"); + + m_contentsTextureManager->reduceMemory(hostImpl->resourceProvider()); +} + +// This function commits the CCLayerTreeHost to an impl tree. When modifying +// this function, keep in mind that the function *runs* on the impl thread! Any +// code that is logically a main thread operation, e.g. deletion of a LayerChromium, +// should be delayed until the CCLayerTreeHost::commitComplete, which will run +// after the commit, but on the main thread. +void CCLayerTreeHost::finishCommitOnImplThread(CCLayerTreeHostImpl* hostImpl) +{ + ASSERT(CCProxy::isImplThread()); + + hostImpl->setRootLayer(TreeSynchronizer::synchronizeTrees(rootLayer(), hostImpl->detachLayerTree(), hostImpl)); + + if (!m_hudLayer) + hostImpl->setHudLayer(0); + else + hostImpl->setHudLayer(static_cast<CCHeadsUpDisplayLayerImpl*>(CCLayerTreeHostCommon::findLayerInSubtree(hostImpl->rootLayer(), m_hudLayer->id()))); + + // We may have added an animation during the tree sync. This will cause both layer tree hosts + // to visit their controllers. + if (rootLayer() && m_needsAnimateLayers) + hostImpl->setNeedsAnimateLayers(); + + hostImpl->setSourceFrameNumber(commitNumber()); + hostImpl->setViewportSize(layoutViewportSize(), deviceViewportSize()); + hostImpl->setDeviceScaleFactor(deviceScaleFactor()); + hostImpl->setPageScaleFactorAndLimits(m_pageScaleFactor, m_minPageScaleFactor, m_maxPageScaleFactor); + hostImpl->setBackgroundColor(m_backgroundColor); + hostImpl->setHasTransparentBackground(m_hasTransparentBackground); + + m_commitNumber++; +} + +void CCLayerTreeHost::setFontAtlas(PassOwnPtr<CCFontAtlas> fontAtlas) +{ + m_fontAtlas = fontAtlas; + setNeedsCommit(); +} + +void CCLayerTreeHost::willCommit() +{ + m_client->willCommit(); + if (m_rootLayer && m_settings.showDebugInfo()) { + if (!m_hudLayer) + m_hudLayer = HeadsUpDisplayLayerChromium::create(); + + if (m_fontAtlas) + m_hudLayer->setFontAtlas(m_fontAtlas.release()); + + if (m_hudLayer->parent() != m_rootLayer.get()) { + m_hudLayer->removeFromParent(); + m_rootLayer->addChild(m_hudLayer); + } + } +} + +void CCLayerTreeHost::commitComplete() +{ + m_deleteTextureAfterCommitList.clear(); + m_client->didCommit(); +} + +PassOwnPtr<CCGraphicsContext> CCLayerTreeHost::createContext() +{ + return m_client->createOutputSurface(); +} + +PassOwnPtr<CCLayerTreeHostImpl> CCLayerTreeHost::createLayerTreeHostImpl(CCLayerTreeHostImplClient* client) +{ + return CCLayerTreeHostImpl::create(m_settings, client); +} + +void CCLayerTreeHost::didLoseContext() +{ + TRACE_EVENT0("cc", "CCLayerTreeHost::didLoseContext"); + ASSERT(CCProxy::isMainThread()); + m_contextLost = true; + m_numFailedRecreateAttempts = 0; + setNeedsCommit(); +} + +bool CCLayerTreeHost::compositeAndReadback(void *pixels, const IntRect& rect) +{ + m_triggerIdleUpdates = false; + bool ret = m_proxy->compositeAndReadback(pixels, rect); + m_triggerIdleUpdates = true; + return ret; +} + +void CCLayerTreeHost::finishAllRendering() +{ + if (!m_rendererInitialized) + return; + m_proxy->finishAllRendering(); +} + +void CCLayerTreeHost::renderingStats(CCRenderingStats& stats) const +{ + stats = m_renderingStats; + m_proxy->implSideRenderingStats(stats); +} + +const RendererCapabilities& CCLayerTreeHost::rendererCapabilities() const +{ + return m_proxy->rendererCapabilities(); +} + +void CCLayerTreeHost::setNeedsAnimate() +{ + ASSERT(CCProxy::hasImplThread()); + m_proxy->setNeedsAnimate(); +} + +void CCLayerTreeHost::setNeedsCommit() +{ + m_proxy->setNeedsCommit(); +} + +void CCLayerTreeHost::setNeedsRedraw() +{ + m_proxy->setNeedsRedraw(); + if (!CCThreadProxy::implThread()) + m_client->scheduleComposite(); +} + +bool CCLayerTreeHost::commitRequested() const +{ + return m_proxy->commitRequested(); +} + +void CCLayerTreeHost::setAnimationEvents(PassOwnPtr<CCAnimationEventsVector> events, double wallClockTime) +{ + ASSERT(CCThreadProxy::isMainThread()); + setAnimationEventsRecursive(*events, m_rootLayer.get(), wallClockTime); +} + +void CCLayerTreeHost::didAddAnimation() +{ + m_needsAnimateLayers = true; + m_proxy->didAddAnimation(); +} + +void CCLayerTreeHost::setRootLayer(PassRefPtr<LayerChromium> rootLayer) +{ + if (m_rootLayer == rootLayer) + return; + + if (m_rootLayer) + m_rootLayer->setLayerTreeHost(0); + m_rootLayer = rootLayer; + if (m_rootLayer) + m_rootLayer->setLayerTreeHost(this); + setNeedsCommit(); +} + +void CCLayerTreeHost::setViewportSize(const IntSize& layoutViewportSize, const IntSize& deviceViewportSize) +{ + if (layoutViewportSize == m_layoutViewportSize && deviceViewportSize == m_deviceViewportSize) + return; + + m_layoutViewportSize = layoutViewportSize; + m_deviceViewportSize = deviceViewportSize; + + setNeedsCommit(); +} + +void CCLayerTreeHost::setPageScaleFactorAndLimits(float pageScaleFactor, float minPageScaleFactor, float maxPageScaleFactor) +{ + if (pageScaleFactor == m_pageScaleFactor && minPageScaleFactor == m_minPageScaleFactor && maxPageScaleFactor == m_maxPageScaleFactor) + return; + + m_pageScaleFactor = pageScaleFactor; + m_minPageScaleFactor = minPageScaleFactor; + m_maxPageScaleFactor = maxPageScaleFactor; + setNeedsCommit(); +} + +void CCLayerTreeHost::setVisible(bool visible) +{ + if (m_visible == visible) + return; + m_visible = visible; + m_proxy->setVisible(visible); +} + +void CCLayerTreeHost::evictAllContentTextures() +{ + ASSERT(CCProxy::isMainThread()); + ASSERT(m_contentsTextureManager.get()); + m_contentsTextureManager->allBackingTexturesWereDeleted(); +} + +void CCLayerTreeHost::startPageScaleAnimation(const IntSize& targetPosition, bool useAnchor, float scale, double durationSec) +{ + m_proxy->startPageScaleAnimation(targetPosition, useAnchor, scale, durationSec); +} + +void CCLayerTreeHost::loseContext(int numTimes) +{ + TRACE_EVENT1("cc", "CCLayerTreeHost::loseCompositorContext", "numTimes", numTimes); + m_numTimesRecreateShouldFail = numTimes - 1; + m_proxy->loseContext(); +} + +CCPrioritizedTextureManager* CCLayerTreeHost::contentsTextureManager() const +{ + return m_contentsTextureManager.get(); +} + +void CCLayerTreeHost::composite() +{ + ASSERT(!CCThreadProxy::implThread()); + static_cast<CCSingleThreadProxy*>(m_proxy.get())->compositeImmediately(); +} + +void CCLayerTreeHost::scheduleComposite() +{ + m_client->scheduleComposite(); +} + +bool CCLayerTreeHost::initializeRendererIfNeeded() +{ + if (!m_rendererInitialized) { + initializeRenderer(); + // If we couldn't initialize, then bail since we're returning to software mode. + if (!m_rendererInitialized) + return false; + } + if (m_contextLost) { + if (recreateContext() != RecreateSucceeded) + return false; + } + return true; +} + + +void CCLayerTreeHost::updateLayers(CCTextureUpdateQueue& queue, size_t memoryAllocationLimitBytes) +{ + ASSERT(m_rendererInitialized); + ASSERT(memoryAllocationLimitBytes); + + if (!rootLayer()) + return; + + if (layoutViewportSize().isEmpty()) + return; + + m_contentsTextureManager->setMaxMemoryLimitBytes(memoryAllocationLimitBytes); + + updateLayers(rootLayer(), queue); +} + +void CCLayerTreeHost::updateLayers(LayerChromium* rootLayer, CCTextureUpdateQueue& queue) +{ + TRACE_EVENT0("cc", "CCLayerTreeHost::updateLayers"); + + LayerList updateList; + + { + TRACE_EVENT0("cc", "CCLayerTreeHost::updateLayers::calcDrawEtc"); + CCLayerTreeHostCommon::calculateDrawTransforms(rootLayer, deviceViewportSize(), m_deviceScaleFactor, rendererCapabilities().maxTextureSize, updateList); + CCLayerTreeHostCommon::calculateVisibleRects(updateList); + } + + // Reset partial texture update requests. + m_partialTextureUpdateRequests = 0; + + bool needMoreUpdates = paintLayerContents(updateList, queue); + if (m_triggerIdleUpdates && needMoreUpdates) + setNeedsCommit(); + + for (size_t i = 0; i < updateList.size(); ++i) + updateList[i]->clearRenderSurface(); +} + +void CCLayerTreeHost::setPrioritiesForSurfaces(size_t surfaceMemoryBytes) +{ + // Surfaces have a place holder for their memory since they are managed + // independantly but should still be tracked and reduce other memory usage. + m_surfaceMemoryPlaceholder->setTextureManager(m_contentsTextureManager.get()); + m_surfaceMemoryPlaceholder->setRequestPriority(CCPriorityCalculator::renderSurfacePriority()); + m_surfaceMemoryPlaceholder->setToSelfManagedMemoryPlaceholder(surfaceMemoryBytes); +} + +void CCLayerTreeHost::setPrioritiesForLayers(const LayerList& updateList) +{ + // Use BackToFront since it's cheap and this isn't order-dependent. + typedef CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, CCLayerIteratorActions::BackToFront> CCLayerIteratorType; + + CCPriorityCalculator calculator; + CCLayerIteratorType end = CCLayerIteratorType::end(&updateList); + for (CCLayerIteratorType it = CCLayerIteratorType::begin(&updateList); it != end; ++it) { + if (it.representsItself()) + it->setTexturePriorities(calculator); + else if (it.representsTargetRenderSurface()) { + if (it->maskLayer()) + it->maskLayer()->setTexturePriorities(calculator); + if (it->replicaLayer() && it->replicaLayer()->maskLayer()) + it->replicaLayer()->maskLayer()->setTexturePriorities(calculator); + } + } +} + +void CCLayerTreeHost::prioritizeTextures(const LayerList& renderSurfaceLayerList, CCOverdrawMetrics& metrics) +{ + m_contentsTextureManager->clearPriorities(); + + size_t memoryForRenderSurfacesMetric = calculateMemoryForRenderSurfaces(renderSurfaceLayerList); + + setPrioritiesForLayers(renderSurfaceLayerList); + setPrioritiesForSurfaces(memoryForRenderSurfacesMetric); + + metrics.didUseContentsTextureMemoryBytes(m_contentsTextureManager->memoryAboveCutoffBytes()); + metrics.didUseRenderSurfaceTextureMemoryBytes(memoryForRenderSurfacesMetric); + + m_contentsTextureManager->prioritizeTextures(); +} + +size_t CCLayerTreeHost::calculateMemoryForRenderSurfaces(const LayerList& updateList) +{ + size_t readbackBytes = 0; + size_t maxBackgroundTextureBytes = 0; + size_t contentsTextureBytes = 0; + + // Start iteration at 1 to skip the root surface as it does not have a texture cost. + for (size_t i = 1; i < updateList.size(); ++i) { + LayerChromium* renderSurfaceLayer = updateList[i].get(); + RenderSurfaceChromium* renderSurface = renderSurfaceLayer->renderSurface(); + + size_t bytes = CCTexture::memorySizeBytes(renderSurface->contentRect().size(), GraphicsContext3D::RGBA); + contentsTextureBytes += bytes; + + if (renderSurfaceLayer->backgroundFilters().isEmpty()) + continue; + + if (bytes > maxBackgroundTextureBytes) + maxBackgroundTextureBytes = bytes; + if (!readbackBytes) + readbackBytes = CCTexture::memorySizeBytes(m_deviceViewportSize, GraphicsContext3D::RGBA); + } + return readbackBytes + maxBackgroundTextureBytes + contentsTextureBytes; +} + +bool CCLayerTreeHost::paintMasksForRenderSurface(LayerChromium* renderSurfaceLayer, CCTextureUpdateQueue& queue) +{ + // Note: Masks and replicas only exist for layers that own render surfaces. If we reach this point + // in code, we already know that at least something will be drawn into this render surface, so the + // mask and replica should be painted. + + bool needMoreUpdates = false; + LayerChromium* maskLayer = renderSurfaceLayer->maskLayer(); + if (maskLayer) { + maskLayer->update(queue, 0, m_renderingStats); + needMoreUpdates |= maskLayer->needMoreUpdates(); + } + + LayerChromium* replicaMaskLayer = renderSurfaceLayer->replicaLayer() ? renderSurfaceLayer->replicaLayer()->maskLayer() : 0; + if (replicaMaskLayer) { + replicaMaskLayer->update(queue, 0, m_renderingStats); + needMoreUpdates |= replicaMaskLayer->needMoreUpdates(); + } + return needMoreUpdates; +} + +bool CCLayerTreeHost::paintLayerContents(const LayerList& renderSurfaceLayerList, CCTextureUpdateQueue& queue) +{ + // Use FrontToBack to allow for testing occlusion and performing culling during the tree walk. + typedef CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, CCLayerIteratorActions::FrontToBack> CCLayerIteratorType; + + bool needMoreUpdates = false; + bool recordMetricsForFrame = true; // FIXME: In the future, disable this when about:tracing is off. + CCOcclusionTracker occlusionTracker(m_rootLayer->renderSurface()->contentRect(), recordMetricsForFrame); + occlusionTracker.setMinimumTrackingSize(m_settings.minimumOcclusionTrackingSize); + + prioritizeTextures(renderSurfaceLayerList, occlusionTracker.overdrawMetrics()); + + CCLayerIteratorType end = CCLayerIteratorType::end(&renderSurfaceLayerList); + for (CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); it != end; ++it) { + occlusionTracker.enterLayer(it); + + if (it.representsTargetRenderSurface()) { + ASSERT(it->renderSurface()->drawOpacity() || it->renderSurface()->drawOpacityIsAnimating()); + needMoreUpdates |= paintMasksForRenderSurface(*it, queue); + } else if (it.representsItself()) { + ASSERT(!it->bounds().isEmpty()); + it->update(queue, &occlusionTracker, m_renderingStats); + needMoreUpdates |= it->needMoreUpdates(); + } + + occlusionTracker.leaveLayer(it); + } + + occlusionTracker.overdrawMetrics().recordMetrics(this); + + return needMoreUpdates; +} + +static LayerChromium* findFirstScrollableLayer(LayerChromium* layer) +{ + if (!layer) + return 0; + + if (layer->scrollable()) + return layer; + + for (size_t i = 0; i < layer->children().size(); ++i) { + LayerChromium* found = findFirstScrollableLayer(layer->children()[i].get()); + if (found) + return found; + } + + return 0; +} + +void CCLayerTreeHost::applyScrollAndScale(const CCScrollAndScaleSet& info) +{ + if (!m_rootLayer) + return; + + LayerChromium* rootScrollLayer = findFirstScrollableLayer(m_rootLayer.get()); + IntSize rootScrollDelta; + + for (size_t i = 0; i < info.scrolls.size(); ++i) { + LayerChromium* layer = CCLayerTreeHostCommon::findLayerInSubtree(m_rootLayer.get(), info.scrolls[i].layerId); + if (!layer) + continue; + if (layer == rootScrollLayer) + rootScrollDelta += info.scrolls[i].scrollDelta; + else + layer->scrollBy(info.scrolls[i].scrollDelta); + } + if (!rootScrollDelta.isZero() || info.pageScaleDelta != 1) + m_client->applyScrollAndScale(rootScrollDelta, info.pageScaleDelta); +} + +void CCLayerTreeHost::startRateLimiter(WebKit::WebGraphicsContext3D* context) +{ + if (m_animating) + return; + + ASSERT(context); + RateLimiterMap::iterator it = m_rateLimiters.find(context); + if (it != m_rateLimiters.end()) + it->second->start(); + else { + RefPtr<RateLimiter> rateLimiter = RateLimiter::create(context, this); + m_rateLimiters.set(context, rateLimiter); + rateLimiter->start(); + } +} + +void CCLayerTreeHost::stopRateLimiter(WebKit::WebGraphicsContext3D* context) +{ + RateLimiterMap::iterator it = m_rateLimiters.find(context); + if (it != m_rateLimiters.end()) { + it->second->stop(); + m_rateLimiters.remove(it); + } +} + +void CCLayerTreeHost::rateLimit() +{ + // Force a no-op command on the compositor context, so that any ratelimiting commands will wait for the compositing + // context, and therefore for the SwapBuffers. + m_proxy->forceSerializeOnSwapBuffers(); +} + +bool CCLayerTreeHost::bufferedUpdates() +{ + return m_settings.maxPartialTextureUpdates != numeric_limits<size_t>::max(); +} + +bool CCLayerTreeHost::requestPartialTextureUpdate() +{ + if (m_partialTextureUpdateRequests >= m_settings.maxPartialTextureUpdates) + return false; + + m_partialTextureUpdateRequests++; + return true; +} + +void CCLayerTreeHost::deleteTextureAfterCommit(PassOwnPtr<CCPrioritizedTexture> texture) +{ + m_deleteTextureAfterCommitList.append(texture); +} + +void CCLayerTreeHost::setDeviceScaleFactor(float deviceScaleFactor) +{ + if (deviceScaleFactor == m_deviceScaleFactor) + return; + m_deviceScaleFactor = deviceScaleFactor; + + setNeedsCommit(); +} + +void CCLayerTreeHost::animateLayers(double monotonicTime) +{ + if (!CCSettings::acceleratedAnimationEnabled() || !m_needsAnimateLayers) + return; + + TRACE_EVENT0("cc", "CCLayerTreeHostImpl::animateLayers"); + m_needsAnimateLayers = animateLayersRecursive(m_rootLayer.get(), monotonicTime); +} + +bool CCLayerTreeHost::animateLayersRecursive(LayerChromium* current, double monotonicTime) +{ + if (!current) + return false; + + bool subtreeNeedsAnimateLayers = false; + CCLayerAnimationController* currentController = current->layerAnimationController(); + currentController->animate(monotonicTime, 0); + + // 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) { + if (animateLayersRecursive(current->children()[i].get(), monotonicTime)) + subtreeNeedsAnimateLayers = true; + } + + return subtreeNeedsAnimateLayers; +} + +void CCLayerTreeHost::setAnimationEventsRecursive(const CCAnimationEventsVector& events, LayerChromium* layer, double wallClockTime) +{ + if (!layer) + return; + + for (size_t eventIndex = 0; eventIndex < events.size(); ++eventIndex) { + if (layer->id() == events[eventIndex].layerId) { + if (events[eventIndex].type == CCAnimationEvent::Started) + layer->notifyAnimationStarted(events[eventIndex], wallClockTime); + else + layer->notifyAnimationFinished(wallClockTime); + } + } + + for (size_t childIndex = 0; childIndex < layer->children().size(); ++childIndex) + setAnimationEventsRecursive(events, layer->children()[childIndex].get(), wallClockTime); +} + +} // namespace WebCore diff --git a/cc/CCLayerTreeHost.h b/cc/CCLayerTreeHost.h new file mode 100644 index 0000000..11e1f9b --- /dev/null +++ b/cc/CCLayerTreeHost.h @@ -0,0 +1,318 @@ +// 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. + +#ifndef CCLayerTreeHost_h +#define CCLayerTreeHost_h + +#include "CCAnimationEvents.h" +#include "CCGraphicsContext.h" +#include "CCLayerTreeHostCommon.h" +#include "CCOcclusionTracker.h" +#include "CCPrioritizedTextureManager.h" +#include "CCProxy.h" +#include "CCRenderingStats.h" +#include "IntRect.h" +#include "RateLimiter.h" +#include "SkColor.h" +#include <limits> +#include <wtf/HashMap.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class CCFontAtlas; +class CCLayerChromium; +class CCLayerTreeHostImpl; +class CCLayerTreeHostImplClient; +class CCPrioritizedTextureManager; +class CCTextureUpdateQueue; +class HeadsUpDisplayLayerChromium; +class Region; +struct CCScrollAndScaleSet; + +class CCLayerTreeHostClient { +public: + virtual void willBeginFrame() = 0; + // Marks finishing compositing-related tasks on the main thread. In threaded mode, this corresponds to didCommit(). + virtual void didBeginFrame() = 0; + virtual void animate(double frameBeginTime) = 0; + virtual void layout() = 0; + virtual void applyScrollAndScale(const IntSize& scrollDelta, float pageScale) = 0; + virtual PassOwnPtr<WebKit::WebCompositorOutputSurface> createOutputSurface() = 0; + virtual void didRecreateOutputSurface(bool success) = 0; + virtual void willCommit() = 0; + virtual void didCommit() = 0; + virtual void didCommitAndDrawFrame() = 0; + virtual void didCompleteSwapBuffers() = 0; + + // Used only in the single-threaded path. + virtual void scheduleComposite() = 0; + +protected: + virtual ~CCLayerTreeHostClient() { } +}; + +struct CCLayerTreeSettings { + CCLayerTreeSettings() + : acceleratePainting(false) + , showFPSCounter(false) + , showPlatformLayerTree(false) + , showPaintRects(false) + , showPropertyChangedRects(false) + , showSurfaceDamageRects(false) + , showScreenSpaceRects(false) + , showReplicaScreenSpaceRects(false) + , showOccludingRects(false) + , renderVSyncEnabled(true) + , refreshRate(0) + , maxPartialTextureUpdates(std::numeric_limits<size_t>::max()) + , defaultTileSize(IntSize(256, 256)) + , maxUntiledLayerSize(IntSize(512, 512)) + , minimumOcclusionTrackingSize(IntSize(160, 160)) + { } + + bool acceleratePainting; + bool showFPSCounter; + bool showPlatformLayerTree; + bool showPaintRects; + bool showPropertyChangedRects; + bool showSurfaceDamageRects; + bool showScreenSpaceRects; + bool showReplicaScreenSpaceRects; + bool showOccludingRects; + bool renderVSyncEnabled; + double refreshRate; + size_t maxPartialTextureUpdates; + IntSize defaultTileSize; + IntSize maxUntiledLayerSize; + IntSize minimumOcclusionTrackingSize; + + bool showDebugInfo() const { return showPlatformLayerTree || showFPSCounter || showDebugRects(); } + bool showDebugRects() const { return showPaintRects || showPropertyChangedRects || showSurfaceDamageRects || showScreenSpaceRects || showReplicaScreenSpaceRects || showOccludingRects; } +}; + +// Provides information on an Impl's rendering capabilities back to the CCLayerTreeHost +struct RendererCapabilities { + RendererCapabilities() + : bestTextureFormat(0) + , contextHasCachedFrontBuffer(false) + , usingPartialSwap(false) + , usingAcceleratedPainting(false) + , usingSetVisibility(false) + , usingSwapCompleteCallback(false) + , usingGpuMemoryManager(false) + , usingDiscardFramebuffer(false) + , usingEglImage(false) + , maxTextureSize(0) { } + + GC3Denum bestTextureFormat; + bool contextHasCachedFrontBuffer; + bool usingPartialSwap; + bool usingAcceleratedPainting; + bool usingSetVisibility; + bool usingSwapCompleteCallback; + bool usingGpuMemoryManager; + bool usingDiscardFramebuffer; + bool usingEglImage; + int maxTextureSize; +}; + +class CCLayerTreeHost : public RateLimiterClient { + WTF_MAKE_NONCOPYABLE(CCLayerTreeHost); +public: + static PassOwnPtr<CCLayerTreeHost> create(CCLayerTreeHostClient*, const CCLayerTreeSettings&); + virtual ~CCLayerTreeHost(); + + void setSurfaceReady(); + + // Returns true if any CCLayerTreeHost is alive. + static bool anyLayerTreeHostInstanceExists(); + + static bool needsFilterContext() { return s_needsFilterContext; } + static void setNeedsFilterContext(bool needsFilterContext) { s_needsFilterContext = needsFilterContext; } + bool needsSharedContext() const { return needsFilterContext() || settings().acceleratePainting; } + + // CCLayerTreeHost interface to CCProxy. + void willBeginFrame() { m_client->willBeginFrame(); } + void didBeginFrame() { m_client->didBeginFrame(); } + void updateAnimations(double monotonicFrameBeginTime); + void layout(); + void beginCommitOnImplThread(CCLayerTreeHostImpl*); + void finishCommitOnImplThread(CCLayerTreeHostImpl*); + void willCommit(); + void commitComplete(); + PassOwnPtr<CCGraphicsContext> createContext(); + virtual PassOwnPtr<CCLayerTreeHostImpl> createLayerTreeHostImpl(CCLayerTreeHostImplClient*); + void didLoseContext(); + enum RecreateResult { + RecreateSucceeded, + RecreateFailedButTryAgain, + RecreateFailedAndGaveUp, + }; + RecreateResult recreateContext(); + void didCommitAndDrawFrame() { m_client->didCommitAndDrawFrame(); } + void didCompleteSwapBuffers() { m_client->didCompleteSwapBuffers(); } + void deleteContentsTexturesOnImplThread(CCResourceProvider*); + virtual void acquireLayerTextures(); + // Returns false if we should abort this frame due to initialization failure. + bool initializeRendererIfNeeded(); + void updateLayers(CCTextureUpdateQueue&, size_t contentsMemoryLimitBytes); + + CCLayerTreeHostClient* client() { return m_client; } + + int compositorIdentifier() const { return m_compositorIdentifier; } + + // Only used when compositing on the main thread. + void composite(); + void scheduleComposite(); + + // Composites and attempts to read back the result into the provided + // buffer. If it wasn't possible, e.g. due to context lost, will return + // false. + bool compositeAndReadback(void *pixels, const IntRect&); + + void finishAllRendering(); + + int commitNumber() const { return m_commitNumber; } + + void renderingStats(CCRenderingStats&) const; + + const RendererCapabilities& rendererCapabilities() const; + + // Test only hook + void loseContext(int numTimes); + + void setNeedsAnimate(); + // virtual for testing + virtual void setNeedsCommit(); + void setNeedsRedraw(); + bool commitRequested() const; + + void setAnimationEvents(PassOwnPtr<CCAnimationEventsVector>, double wallClockTime); + virtual void didAddAnimation(); + + LayerChromium* rootLayer() { return m_rootLayer.get(); } + const LayerChromium* rootLayer() const { return m_rootLayer.get(); } + void setRootLayer(PassRefPtr<LayerChromium>); + + const CCLayerTreeSettings& settings() const { return m_settings; } + + void setViewportSize(const IntSize& layoutViewportSize, const IntSize& deviceViewportSize); + + const IntSize& layoutViewportSize() const { return m_layoutViewportSize; } + const IntSize& deviceViewportSize() const { return m_deviceViewportSize; } + + void setPageScaleFactorAndLimits(float pageScaleFactor, float minPageScaleFactor, float maxPageScaleFactor); + + void setBackgroundColor(SkColor color) { m_backgroundColor = color; } + + void setHasTransparentBackground(bool transparent) { m_hasTransparentBackground = transparent; } + + CCPrioritizedTextureManager* contentsTextureManager() const; + + // This will cause contents texture manager to evict all textures, but + // without deleting them. This happens after all content textures have + // already been deleted on impl, after getting a 0 allocation limit. + // Set during a commit, but before updateLayers. + void evictAllContentTextures(); + + bool visible() const { return m_visible; } + void setVisible(bool); + + void startPageScaleAnimation(const IntSize& targetPosition, bool useAnchor, float scale, double durationSec); + + void applyScrollAndScale(const CCScrollAndScaleSet&); + + void startRateLimiter(WebKit::WebGraphicsContext3D*); + void stopRateLimiter(WebKit::WebGraphicsContext3D*); + + // RateLimitClient implementation + virtual void rateLimit() OVERRIDE; + + bool bufferedUpdates(); + bool requestPartialTextureUpdate(); + void deleteTextureAfterCommit(PassOwnPtr<CCPrioritizedTexture>); + + void setDeviceScaleFactor(float); + float deviceScaleFactor() const { return m_deviceScaleFactor; } + + void setFontAtlas(PassOwnPtr<CCFontAtlas>); + +protected: + CCLayerTreeHost(CCLayerTreeHostClient*, const CCLayerTreeSettings&); + bool initialize(); + +private: + typedef Vector<RefPtr<LayerChromium> > LayerList; + typedef Vector<OwnPtr<CCPrioritizedTexture> > TextureList; + + void initializeRenderer(); + + void update(LayerChromium*, CCTextureUpdateQueue&, const CCOcclusionTracker*); + bool paintLayerContents(const LayerList&, CCTextureUpdateQueue&); + bool paintMasksForRenderSurface(LayerChromium*, CCTextureUpdateQueue&); + + void updateLayers(LayerChromium*, CCTextureUpdateQueue&); + + void prioritizeTextures(const LayerList&, CCOverdrawMetrics&); + void setPrioritiesForSurfaces(size_t surfaceMemoryBytes); + void setPrioritiesForLayers(const LayerList&); + size_t calculateMemoryForRenderSurfaces(const LayerList& updateList); + + void animateLayers(double monotonicTime); + bool animateLayersRecursive(LayerChromium* current, double monotonicTime); + void setAnimationEventsRecursive(const CCAnimationEventsVector&, LayerChromium*, double wallClockTime); + + int m_compositorIdentifier; + + bool m_animating; + bool m_needsAnimateLayers; + + CCLayerTreeHostClient* m_client; + + int m_commitNumber; + CCRenderingStats m_renderingStats; + + OwnPtr<CCProxy> m_proxy; + bool m_rendererInitialized; + bool m_contextLost; + int m_numTimesRecreateShouldFail; + int m_numFailedRecreateAttempts; + + RefPtr<LayerChromium> m_rootLayer; + RefPtr<HeadsUpDisplayLayerChromium> m_hudLayer; + OwnPtr<CCFontAtlas> m_fontAtlas; + + OwnPtr<CCPrioritizedTextureManager> m_contentsTextureManager; + OwnPtr<CCPrioritizedTexture> m_surfaceMemoryPlaceholder; + + CCLayerTreeSettings m_settings; + + IntSize m_layoutViewportSize; + IntSize m_deviceViewportSize; + float m_deviceScaleFactor; + + bool m_visible; + + typedef HashMap<WebKit::WebGraphicsContext3D*, RefPtr<RateLimiter> > RateLimiterMap; + RateLimiterMap m_rateLimiters; + + float m_pageScaleFactor; + float m_minPageScaleFactor, m_maxPageScaleFactor; + bool m_triggerIdleUpdates; + + SkColor m_backgroundColor; + bool m_hasTransparentBackground; + + TextureList m_deleteTextureAfterCommitList; + size_t m_partialTextureUpdateRequests; + + static bool s_needsFilterContext; +}; + +} + +#endif diff --git a/cc/CCLayerTreeHostCommon.cpp b/cc/CCLayerTreeHostCommon.cpp new file mode 100644 index 0000000..146f796 --- /dev/null +++ b/cc/CCLayerTreeHostCommon.cpp @@ -0,0 +1,888 @@ +// 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 "config.h" + +#include "CCLayerTreeHostCommon.h" + +#include "CCLayerImpl.h" +#include "CCLayerIterator.h" +#include "CCLayerSorter.h" +#include "CCMathUtil.h" +#include "CCRenderSurface.h" +#include "FloatQuad.h" +#include "IntRect.h" +#include "LayerChromium.h" +#include "RenderSurfaceChromium.h" +#include <public/WebTransformationMatrix.h> + +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +IntRect CCLayerTreeHostCommon::calculateVisibleRect(const IntRect& targetSurfaceRect, const IntRect& layerBoundRect, const WebTransformationMatrix& transform) +{ + // Is this layer fully contained within the target surface? + IntRect layerInSurfaceSpace = CCMathUtil::mapClippedRect(transform, layerBoundRect); + if (targetSurfaceRect.contains(layerInSurfaceSpace)) + return layerBoundRect; + + // If the layer doesn't fill up the entire surface, then find the part of + // the surface rect where the layer could be visible. This avoids trying to + // project surface rect points that are behind the projection point. + IntRect minimalSurfaceRect = targetSurfaceRect; + minimalSurfaceRect.intersect(layerInSurfaceSpace); + + // Project the corners of the target surface rect into the layer space. + // This bounding rectangle may be larger than it needs to be (being + // axis-aligned), but is a reasonable filter on the space to consider. + // Non-invertible transforms will create an empty rect here. + const WebTransformationMatrix surfaceToLayer = transform.inverse(); + IntRect layerRect = enclosingIntRect(CCMathUtil::projectClippedRect(surfaceToLayer, FloatRect(minimalSurfaceRect))); + layerRect.intersect(layerBoundRect); + return layerRect; +} + +template<typename LayerType> +static inline bool layerIsInExisting3DRenderingContext(LayerType* layer) +{ + // According to current W3C spec on CSS transforms, a layer is part of an established + // 3d rendering context if its parent has transform-style of preserves-3d. + return layer->parent() && layer->parent()->preserves3D(); +} + +template<typename LayerType> +static bool layerIsRootOfNewRenderingContext(LayerType* layer) +{ + // According to current W3C spec on CSS transforms (Section 6.1), a layer is the + // beginning of 3d rendering context if its parent does not have transform-style: + // preserve-3d, but this layer itself does. + if (layer->parent()) + return !layer->parent()->preserves3D() && layer->preserves3D(); + + return layer->preserves3D(); +} + +template<typename LayerType> +static bool isLayerBackFaceVisible(LayerType* layer) +{ + // The current W3C spec on CSS transforms says that backface visibility should be + // determined differently depending on whether the layer is in a "3d rendering + // context" or not. For Chromium code, we can determine whether we are in a 3d + // rendering context by checking if the parent preserves 3d. + + if (layerIsInExisting3DRenderingContext(layer)) + return layer->drawTransform().isBackFaceVisible(); + + // In this case, either the layer establishes a new 3d rendering context, or is not in + // a 3d rendering context at all. + return layer->transform().isBackFaceVisible(); +} + +template<typename LayerType> +static bool isSurfaceBackFaceVisible(LayerType* layer, const WebTransformationMatrix& drawTransform) +{ + if (layerIsInExisting3DRenderingContext(layer)) + return drawTransform.isBackFaceVisible(); + + if (layerIsRootOfNewRenderingContext(layer)) + return layer->transform().isBackFaceVisible(); + + // If the renderSurface is not part of a new or existing rendering context, then the + // layers that contribute to this surface will decide back-face visibility for themselves. + return false; +} + +template<typename LayerType> +static inline bool layerClipsSubtree(LayerType* layer) +{ + return layer->masksToBounds() || layer->maskLayer(); +} + +template<typename LayerType> +static IntRect calculateVisibleContentRect(LayerType* layer) +{ + ASSERT(layer->renderTarget()); + + IntRect targetSurfaceRect = layer->renderTarget()->renderSurface()->contentRect(); + + targetSurfaceRect.intersect(layer->drawableContentRect()); + + if (targetSurfaceRect.isEmpty() || layer->contentBounds().isEmpty()) + return IntRect(); + + const IntRect contentRect = IntRect(IntPoint(), layer->contentBounds()); + IntRect visibleContentRect = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, contentRect, layer->drawTransform()); + return visibleContentRect; +} + +static bool isScaleOrTranslation(const WebTransformationMatrix& m) +{ + return !m.m12() && !m.m13() && !m.m14() + && !m.m21() && !m.m23() && !m.m24() + && !m.m31() && !m.m32() && !m.m43() + && m.m44(); +} + +static inline bool transformToParentIsKnown(CCLayerImpl*) +{ + return true; +} + +static inline bool transformToParentIsKnown(LayerChromium* layer) +{ + return !layer->transformIsAnimating(); +} + +static inline bool transformToScreenIsKnown(CCLayerImpl*) +{ + return true; +} + +static inline bool transformToScreenIsKnown(LayerChromium* layer) +{ + return !layer->screenSpaceTransformIsAnimating(); +} + +template<typename LayerType> +static bool layerShouldBeSkipped(LayerType* layer) +{ + // Layers can be skipped if any of these conditions are met. + // - does not draw content. + // - is transparent + // - has empty bounds + // - the layer is not double-sided, but its back face is visible. + // + // Some additional conditions need to be computed at a later point after the recursion is finished. + // - the intersection of render surface content and layer clipRect is empty + // - the visibleContentRect is empty + // + // Note, if the layer should not have been drawn due to being fully transparent, + // we would have skipped the entire subtree and never made it into this function, + // so it is safe to omit this check here. + + if (!layer->drawsContent() || layer->bounds().isEmpty()) + return true; + + LayerType* backfaceTestLayer = layer; + if (layer->useParentBackfaceVisibility()) { + ASSERT(layer->parent()); + ASSERT(!layer->parent()->useParentBackfaceVisibility()); + backfaceTestLayer = layer->parent(); + } + + // The layer should not be drawn if (1) it is not double-sided and (2) the back of the layer is known to be facing the screen. + if (!backfaceTestLayer->doubleSided() && transformToScreenIsKnown(backfaceTestLayer) && isLayerBackFaceVisible(backfaceTestLayer)) + return true; + + return false; +} + +static inline bool subtreeShouldBeSkipped(CCLayerImpl* layer) +{ + // The opacity of a layer always applies to its children (either implicitly + // via a render surface or explicitly if the parent preserves 3D), so the + // entire subtree can be skipped if this layer is fully transparent. + return !layer->opacity(); +} + +static inline bool subtreeShouldBeSkipped(LayerChromium* layer) +{ + // If the opacity is being animated then the opacity on the main thread is unreliable + // (since the impl thread may be using a different opacity), so it should not be trusted. + // In particular, it should not cause the subtree to be skipped. + return !layer->opacity() && !layer->opacityIsAnimating(); +} + +template<typename LayerType> +static bool subtreeShouldRenderToSeparateSurface(LayerType* layer, bool axisAlignedWithRespectToParent) +{ + // The root layer has a special render surface that is set up externally, so + // it shouldn't be treated as a surface in this code. + if (!layer->parent()) + return false; + + // Cache this value, because otherwise it walks the entire subtree several times. + bool descendantDrawsContent = layer->descendantDrawsContent(); + + // + // A layer and its descendants should render onto a new RenderSurface if any of these rules hold: + // + + // If we force it. + if (layer->forceRenderSurface()) + return true; + + // If the layer uses a mask. + if (layer->maskLayer()) + return true; + + // If the layer has a reflection. + if (layer->replicaLayer()) + return true; + + // If the layer uses a CSS filter. + if (!layer->filters().isEmpty() || !layer->backgroundFilters().isEmpty()) + return true; + + // If the layer flattens its subtree (i.e. the layer doesn't preserve-3d), but it is + // treated as a 3D object by its parent (i.e. parent does preserve-3d). + if (layerIsInExisting3DRenderingContext(layer) && !layer->preserves3D() && descendantDrawsContent) + return true; + + // If the layer clips its descendants but it is not axis-aligned with respect to its parent. + if (layerClipsSubtree(layer) && !axisAlignedWithRespectToParent && descendantDrawsContent) + return true; + + // If the layer has opacity != 1 and does not have a preserves-3d transform style. + if (layer->opacity() != 1 && !layer->preserves3D() && descendantDrawsContent) + return true; + + return false; +} + +WebTransformationMatrix computeScrollCompensationForThisLayer(CCLayerImpl* scrollingLayer, const WebTransformationMatrix& parentMatrix) +{ + // For every layer that has non-zero scrollDelta, we have to compute a transform that can undo the + // scrollDelta translation. In particular, we want this matrix to premultiply a fixed-position layer's + // parentMatrix, so we design this transform in three steps as follows. The steps described here apply + // from right-to-left, so Step 1 would be the right-most matrix: + // + // Step 1. transform from target surface space to the exact space where scrollDelta is actually applied. + // -- this is inverse of the matrix in step 3 + // Step 2. undo the scrollDelta + // -- this is just a translation by scrollDelta. + // Step 3. transform back to target surface space. + // -- this transform is the "partialLayerOriginTransform" = (parentMatrix * scale(layer->pageScaleDelta())); + // + // These steps create a matrix that both start and end in targetSurfaceSpace. So this matrix can + // pre-multiply any fixed-position layer's drawTransform to undo the scrollDeltas -- as long as + // that fixed position layer is fixed onto the same renderTarget as this scrollingLayer. + // + + WebTransformationMatrix partialLayerOriginTransform = parentMatrix; + partialLayerOriginTransform.scale(scrollingLayer->pageScaleDelta()); + + WebTransformationMatrix scrollCompensationForThisLayer = partialLayerOriginTransform; // Step 3 + scrollCompensationForThisLayer.translate(scrollingLayer->scrollDelta().width(), scrollingLayer->scrollDelta().height()); // Step 2 + scrollCompensationForThisLayer.multiply(partialLayerOriginTransform.inverse()); // Step 1 + return scrollCompensationForThisLayer; +} + +WebTransformationMatrix computeScrollCompensationMatrixForChildren(LayerChromium* currentLayer, const WebTransformationMatrix& currentParentMatrix, const WebTransformationMatrix& currentScrollCompensation) +{ + // The main thread (i.e. LayerChromium) does not need to worry about scroll compensation. + // So we can just return an identity matrix here. + return WebTransformationMatrix(); +} + +WebTransformationMatrix computeScrollCompensationMatrixForChildren(CCLayerImpl* layer, const WebTransformationMatrix& parentMatrix, const WebTransformationMatrix& currentScrollCompensationMatrix) +{ + // "Total scroll compensation" is the transform needed to cancel out all scrollDelta translations that + // occurred since the nearest container layer, even if there are renderSurfaces in-between. + // + // There are some edge cases to be aware of, that are not explicit in the code: + // - A layer that is both a fixed-position and container should not be its own container, instead, that means + // it is fixed to an ancestor, and is a container for any fixed-position descendants. + // - A layer that is a fixed-position container and has a renderSurface should behave the same as a container + // without a renderSurface, the renderSurface is irrelevant in that case. + // - A layer that does not have an explicit container is simply fixed to the viewport + // (i.e. the root renderSurface, and it would still compensate for root layer's scrollDelta). + // - If the fixed-position layer has its own renderSurface, then the renderSurface is + // the one who gets fixed. + // + // This function needs to be called AFTER layers create their own renderSurfaces. + // + + // Avoid the overheads (including stack allocation and matrix initialization/copy) if we know that the scroll compensation doesn't need to be reset or adjusted. + if (!layer->isContainerForFixedPositionLayers() && layer->scrollDelta().isZero() && !layer->renderSurface()) + return currentScrollCompensationMatrix; + + // Start as identity matrix. + WebTransformationMatrix nextScrollCompensationMatrix; + + // If this layer is not a container, then it inherits the existing scroll compensations. + if (!layer->isContainerForFixedPositionLayers()) + nextScrollCompensationMatrix = currentScrollCompensationMatrix; + + // If the current layer has a non-zero scrollDelta, then we should compute its local scrollCompensation + // and accumulate it to the nextScrollCompensationMatrix. + if (!layer->scrollDelta().isZero()) { + WebTransformationMatrix scrollCompensationForThisLayer = computeScrollCompensationForThisLayer(layer, parentMatrix); + nextScrollCompensationMatrix.multiply(scrollCompensationForThisLayer); + } + + // If the layer created its own renderSurface, we have to adjust nextScrollCompensationMatrix. + // The adjustment allows us to continue using the scrollCompensation on the next surface. + // Step 1 (right-most in the math): transform from the new surface to the original ancestor surface + // Step 2: apply the scroll compensation + // Step 3: transform back to the new surface. + if (layer->renderSurface() && !nextScrollCompensationMatrix.isIdentity()) + nextScrollCompensationMatrix = layer->renderSurface()->drawTransform().inverse() * nextScrollCompensationMatrix * layer->renderSurface()->drawTransform(); + + return nextScrollCompensationMatrix; +} + +// Should be called just before the recursive calculateDrawTransformsInternal(). +template<typename LayerType, typename LayerList> +void setupRootLayerAndSurfaceForRecursion(LayerType* rootLayer, LayerList& renderSurfaceLayerList, const IntSize& deviceViewportSize) +{ + if (!rootLayer->renderSurface()) + rootLayer->createRenderSurface(); + + rootLayer->renderSurface()->setContentRect(IntRect(IntPoint::zero(), deviceViewportSize)); + rootLayer->renderSurface()->clearLayerList(); + + ASSERT(renderSurfaceLayerList.isEmpty()); + renderSurfaceLayerList.append(rootLayer); +} + +// Recursively walks the layer tree starting at the given node and computes all the +// necessary transformations, clipRects, render surfaces, etc. +template<typename LayerType, typename LayerList, typename RenderSurfaceType, typename LayerSorter> +static void calculateDrawTransformsInternal(LayerType* layer, LayerType* rootLayer, const WebTransformationMatrix& parentMatrix, + const WebTransformationMatrix& fullHierarchyMatrix, const WebTransformationMatrix& currentScrollCompensationMatrix, + const IntRect& clipRectFromAncestor, bool ancestorClipsSubtree, + RenderSurfaceType* nearestAncestorThatMovesPixels, LayerList& renderSurfaceLayerList, LayerList& layerList, + LayerSorter* layerSorter, int maxTextureSize, float deviceScaleFactor, IntRect& drawableContentRectOfSubtree) +{ + // This function computes the new matrix transformations recursively for this + // layer and all its descendants. It also computes the appropriate render surfaces. + // Some important points to remember: + // + // 0. Here, transforms are notated in Matrix x Vector order, and in words we describe what + // the transform does from left to right. + // + // 1. In our terminology, the "layer origin" refers to the top-left corner of a layer, and the + // positive Y-axis points downwards. This interpretation is valid because the orthographic + // projection applied at draw time flips the Y axis appropriately. + // + // 2. The anchor point, when given as a FloatPoint object, is specified in "unit layer space", + // where the bounds of the layer map to [0, 1]. However, as a WebTransformationMatrix object, + // the transform to the anchor point is specified in "pixel layer space", where the bounds + // of the layer map to [bounds.width(), bounds.height()]. + // + // 3. Definition of various transforms used: + // M[parent] is the parent matrix, with respect to the nearest render surface, passed down recursively. + // M[root] is the full hierarchy, with respect to the root, passed down recursively. + // Tr[origin] is the translation matrix from the parent's origin to this layer's origin. + // Tr[origin2anchor] is the translation from the layer's origin to its anchor point + // Tr[origin2center] is the translation from the layer's origin to its center + // M[layer] is the layer's matrix (applied at the anchor point) + // M[sublayer] is the layer's sublayer transform (applied at the layer's center) + // Tr[anchor2center] is the translation offset from the anchor point and the center of the layer + // S[content2layer] is the ratio of a layer's contentBounds() to its bounds(). + // + // Some shortcuts and substitutions are used in the code to reduce matrix multiplications: + // Tr[anchor2center] = Tr[origin2anchor].inverse() * Tr[origin2center] + // + // Some composite transforms can help in understanding the sequence of transforms: + // compositeLayerTransform = Tr[origin2anchor] * M[layer] * Tr[origin2anchor].inverse() + // compositeSublayerTransform = Tr[origin2center] * M[sublayer] * Tr[origin2center].inverse() + // + // In words, the layer transform is applied about the anchor point, and the sublayer transform is + // applied about the center of the layer. + // + // 4. When a layer (or render surface) is drawn, it is drawn into a "target render surface". Therefore the draw + // transform does not necessarily transform from screen space to local layer space. Instead, the draw transform + // is the transform between the "target render surface space" and local layer space. Note that render surfaces, + // except for the root, also draw themselves into a different target render surface, and so their draw + // transform and origin transforms are also described with respect to the target. + // + // Using these definitions, then: + // + // The draw transform for the layer is: + // M[draw] = M[parent] * Tr[origin] * compositeLayerTransform * S[content2layer] + // = M[parent] * Tr[layer->position()] * M[layer] * Tr[anchor2origin] * S[content2layer] + // + // Interpreting the math left-to-right, this transforms from the layer's render surface to the origin of the layer in content space. + // + // The screen space transform is: + // M[screenspace] = M[root] * Tr[origin] * compositeLayerTransform * S[content2layer] + // = M[root] * Tr[layer->position()] * M[layer] * Tr[origin2anchor].inverse() * S[content2layer] + // + // Interpreting the math left-to-right, this transforms from the root render surface's content space to the local layer's origin in layer space. + // + // The transform hierarchy that is passed on to children (i.e. the child's parentMatrix) is: + // M[parent]_for_child = M[parent] * Tr[origin] * compositeLayerTransform * compositeSublayerTransform + // = M[parent] * Tr[layer->position()] * M[layer] * Tr[anchor2center] * M[sublayer] * Tr[origin2center].inverse() + // = M[draw] * M[sublayer] * Tr[origin2center].inverse() + // + // and a similar matrix for the full hierarchy with respect to the root. + // + // Finally, note that the final matrix used by the shader for the layer is P * M[draw] * S . This final product + // is computed in drawTexturedQuad(), where: + // P is the projection matrix + // S is the scale adjustment (to scale up to the layer size) + // + // When a render surface has a replica layer, that layer's transform is used to draw a second copy of the surface. + // Transforms named here are relative to the surface, unless they specify they are relative to the replica layer. + // + // We will denote a scale by device scale S[deviceScale] + // + // The render surface draw transform to its target surface origin is: + // M[surfaceDraw] = M[owningLayer->Draw] + // + // The render surface origin transform to its the root (screen space) origin is: + // M[surface2root] = M[owningLayer->screenspace] * S[deviceScale].inverse() + // + // The replica draw transform to its target surface origin is: + // M[replicaDraw] = S[deviceScale] * M[surfaceDraw] * Tr[replica->position() + replica->anchor()] * Tr[replica] * Tr[origin2anchor].inverse() * S[contentsScale].inverse() + // + // The replica draw transform to the root (screen space) origin is: + // M[replica2root] = M[surface2root] * Tr[replica->position()] * Tr[replica] * Tr[origin2anchor].inverse() + // + + // If we early-exit anywhere in this function, the drawableContentRect of this subtree should be considered empty. + drawableContentRectOfSubtree = IntRect(); + + if (subtreeShouldBeSkipped(layer)) + return; + + IntRect clipRectForSubtree; + bool subtreeShouldBeClipped = false; + + float drawOpacity = layer->opacity(); + bool drawOpacityIsAnimating = layer->opacityIsAnimating(); + if (layer->parent() && layer->parent()->preserves3D()) { + drawOpacity *= layer->parent()->drawOpacity(); + drawOpacityIsAnimating |= layer->parent()->drawOpacityIsAnimating(); + } + + IntSize bounds = layer->bounds(); + FloatPoint anchorPoint = layer->anchorPoint(); + FloatPoint position = layer->position() - layer->scrollDelta(); + + // Offset between anchor point and the center of the quad. + float centerOffsetX = (0.5 - anchorPoint.x()) * bounds.width(); + float centerOffsetY = (0.5 - anchorPoint.y()) * bounds.height(); + + WebTransformationMatrix layerLocalTransform; + // LT = S[pageScaleDelta] + layerLocalTransform.scale(layer->pageScaleDelta()); + // LT = S[pageScaleDelta] * Tr[origin] * Tr[origin2anchor] + layerLocalTransform.translate3d(position.x() + anchorPoint.x() * bounds.width(), position.y() + anchorPoint.y() * bounds.height(), layer->anchorPointZ()); + // LT = S[pageScaleDelta] * Tr[origin] * Tr[origin2anchor] * M[layer] + layerLocalTransform.multiply(layer->transform()); + // LT = S[pageScaleDelta] * Tr[origin] * Tr[origin2anchor] * M[layer] * Tr[anchor2center] + layerLocalTransform.translate3d(centerOffsetX, centerOffsetY, -layer->anchorPointZ()); + + WebTransformationMatrix combinedTransform = parentMatrix; + combinedTransform.multiply(layerLocalTransform); + + if (layer->fixedToContainerLayer()) { + // Special case: this layer is a composited fixed-position layer; we need to + // explicitly compensate for all ancestors' nonzero scrollDeltas to keep this layer + // fixed correctly. + combinedTransform = currentScrollCompensationMatrix * combinedTransform; + } + + // The drawTransform that gets computed below is effectively the layer's drawTransform, unless + // the layer itself creates a renderSurface. In that case, the renderSurface re-parents the transforms. + WebTransformationMatrix drawTransform = combinedTransform; + // M[draw] = M[parent] * LT * Tr[anchor2center] * Tr[center2origin] + drawTransform.translate(-layer->bounds().width() / 2.0, -layer->bounds().height() / 2.0); + if (!layer->contentBounds().isEmpty() && !layer->bounds().isEmpty()) { + // M[draw] = M[parent] * LT * Tr[anchor2origin] * S[layer2content] + drawTransform.scaleNonUniform(layer->bounds().width() / static_cast<double>(layer->contentBounds().width()), + layer->bounds().height() / static_cast<double>(layer->contentBounds().height())); + } + + // layerScreenSpaceTransform represents the transform between root layer's "screen space" and local content space. + WebTransformationMatrix layerScreenSpaceTransform = fullHierarchyMatrix; + if (!layer->preserves3D()) + CCMathUtil::flattenTransformTo2d(layerScreenSpaceTransform); + layerScreenSpaceTransform.multiply(drawTransform); + layer->setScreenSpaceTransform(layerScreenSpaceTransform); + + bool animatingTransformToTarget = layer->transformIsAnimating(); + bool animatingTransformToScreen = animatingTransformToTarget; + if (layer->parent()) { + animatingTransformToTarget |= layer->parent()->drawTransformIsAnimating(); + animatingTransformToScreen |= layer->parent()->screenSpaceTransformIsAnimating(); + } + + FloatRect contentRect(FloatPoint(), layer->contentBounds()); + + // fullHierarchyMatrix is the matrix that transforms objects between screen space (except projection matrix) and the most recent RenderSurface's space. + // nextHierarchyMatrix will only change if this layer uses a new RenderSurface, otherwise remains the same. + WebTransformationMatrix nextHierarchyMatrix = fullHierarchyMatrix; + WebTransformationMatrix sublayerMatrix; + + if (subtreeShouldRenderToSeparateSurface(layer, isScaleOrTranslation(combinedTransform))) { + // Check back-face visibility before continuing with this surface and its subtree + if (!layer->doubleSided() && transformToParentIsKnown(layer) && isSurfaceBackFaceVisible(layer, combinedTransform)) + return; + + if (!layer->renderSurface()) + layer->createRenderSurface(); + + RenderSurfaceType* renderSurface = layer->renderSurface(); + renderSurface->clearLayerList(); + + // The origin of the new surface is the upper left corner of the layer. + renderSurface->setDrawTransform(drawTransform); + WebTransformationMatrix layerDrawTransform; + layerDrawTransform.scale(deviceScaleFactor); + if (!layer->contentBounds().isEmpty() && !layer->bounds().isEmpty()) { + layerDrawTransform.scaleNonUniform(layer->bounds().width() / static_cast<double>(layer->contentBounds().width()), + layer->bounds().height() / static_cast<double>(layer->contentBounds().height())); + } + layer->setDrawTransform(layerDrawTransform); + + // The sublayer matrix transforms centered layer rects into target + // surface content space. + sublayerMatrix.makeIdentity(); + sublayerMatrix.scale(deviceScaleFactor); + sublayerMatrix.translate(0.5 * bounds.width(), 0.5 * bounds.height()); + + // The opacity value is moved from the layer to its surface, so that the entire subtree properly inherits opacity. + renderSurface->setDrawOpacity(drawOpacity); + renderSurface->setDrawOpacityIsAnimating(drawOpacityIsAnimating); + layer->setDrawOpacity(1); + layer->setDrawOpacityIsAnimating(false); + + renderSurface->setTargetSurfaceTransformsAreAnimating(animatingTransformToTarget); + renderSurface->setScreenSpaceTransformsAreAnimating(animatingTransformToScreen); + animatingTransformToTarget = false; + layer->setDrawTransformIsAnimating(animatingTransformToTarget); + layer->setScreenSpaceTransformIsAnimating(animatingTransformToScreen); + + // Update the aggregate hierarchy matrix to include the transform of the + // newly created RenderSurface. + nextHierarchyMatrix.multiply(renderSurface->drawTransform()); + + // The new renderSurface here will correctly clip the entire subtree. So, we do + // not need to continue propagating the clipping state further down the tree. This + // way, we can avoid transforming clipRects from ancestor target surface space to + // current target surface space that could cause more w < 0 headaches. + subtreeShouldBeClipped = false; + + if (layer->maskLayer()) + layer->maskLayer()->setRenderTarget(layer); + + if (layer->replicaLayer() && layer->replicaLayer()->maskLayer()) + layer->replicaLayer()->maskLayer()->setRenderTarget(layer); + + if (layer->filters().hasFilterThatMovesPixels()) + nearestAncestorThatMovesPixels = renderSurface; + + renderSurface->setNearestAncestorThatMovesPixels(nearestAncestorThatMovesPixels); + + renderSurfaceLayerList.append(layer); + } else { + layer->setDrawTransform(drawTransform); + layer->setDrawTransformIsAnimating(animatingTransformToTarget); + layer->setScreenSpaceTransformIsAnimating(animatingTransformToScreen); + sublayerMatrix = combinedTransform; + + layer->setDrawOpacity(drawOpacity); + layer->setDrawOpacityIsAnimating(drawOpacityIsAnimating); + + if (layer != rootLayer) { + ASSERT(layer->parent()); + layer->clearRenderSurface(); + + // Layers without renderSurfaces directly inherit the ancestor's clip status. + subtreeShouldBeClipped = ancestorClipsSubtree; + if (ancestorClipsSubtree) + clipRectForSubtree = clipRectFromAncestor; + + // Layers that are not their own renderTarget will render into the target of their nearest ancestor. + layer->setRenderTarget(layer->parent()->renderTarget()); + } else { + // FIXME: This root layer special case code should eventually go away. But before that is truly possible, + // tests (or code) related to CCOcclusionTracker need to be adjusted so that they do not require + // the rootLayer to clip; the root layer's RenderSurface would already clip and should be enough. + ASSERT(!layer->parent()); + ASSERT(layer->renderSurface()); + ASSERT(ancestorClipsSubtree); + layer->renderSurface()->setClipRect(clipRectFromAncestor); + subtreeShouldBeClipped = true; + clipRectForSubtree = clipRectFromAncestor; + } + } + + IntRect rectInTargetSpace = enclosingIntRect(CCMathUtil::mapClippedRect(layer->drawTransform(), contentRect)); + + if (layerClipsSubtree(layer)) { + subtreeShouldBeClipped = true; + if (ancestorClipsSubtree && !layer->renderSurface()) { + clipRectForSubtree = clipRectFromAncestor; + clipRectForSubtree.intersect(rectInTargetSpace); + } else + clipRectForSubtree = rectInTargetSpace; + } + + // Flatten to 2D if the layer doesn't preserve 3D. + if (!layer->preserves3D()) + CCMathUtil::flattenTransformTo2d(sublayerMatrix); + + // Apply the sublayer transform at the center of the layer. + sublayerMatrix.multiply(layer->sublayerTransform()); + + // The coordinate system given to children is located at the layer's origin, not the center. + sublayerMatrix.translate3d(-bounds.width() * 0.5, -bounds.height() * 0.5, 0); + + LayerList& descendants = (layer->renderSurface() ? layer->renderSurface()->layerList() : layerList); + + // Any layers that are appended after this point are in the layer's subtree and should be included in the sorting process. + unsigned sortingStartIndex = descendants.size(); + + if (!layerShouldBeSkipped(layer)) + descendants.append(layer); + + WebTransformationMatrix nextScrollCompensationMatrix = computeScrollCompensationMatrixForChildren(layer, parentMatrix, currentScrollCompensationMatrix);; + + IntRect accumulatedDrawableContentRectOfChildren; + for (size_t i = 0; i < layer->children().size(); ++i) { + LayerType* child = layer->children()[i].get(); + IntRect drawableContentRectOfChildSubtree; + calculateDrawTransformsInternal<LayerType, LayerList, RenderSurfaceType, LayerSorter>(child, rootLayer, sublayerMatrix, nextHierarchyMatrix, nextScrollCompensationMatrix, + clipRectForSubtree, subtreeShouldBeClipped, nearestAncestorThatMovesPixels, + renderSurfaceLayerList, descendants, layerSorter, maxTextureSize, deviceScaleFactor, drawableContentRectOfChildSubtree); + if (!drawableContentRectOfChildSubtree.isEmpty()) { + accumulatedDrawableContentRectOfChildren.unite(drawableContentRectOfChildSubtree); + if (child->renderSurface()) + descendants.append(child); + } + } + + // Compute the total drawableContentRect for this subtree (the rect is in targetSurface space) + IntRect localDrawableContentRectOfSubtree = accumulatedDrawableContentRectOfChildren; + if (layer->drawsContent()) + localDrawableContentRectOfSubtree.unite(rectInTargetSpace); + if (subtreeShouldBeClipped) + localDrawableContentRectOfSubtree.intersect(clipRectForSubtree); + + // Compute the layer's drawable content rect (the rect is in targetSurface space) + IntRect drawableContentRectOfLayer = rectInTargetSpace; + if (subtreeShouldBeClipped) + drawableContentRectOfLayer.intersect(clipRectForSubtree); + layer->setDrawableContentRect(drawableContentRectOfLayer); + + // Compute the remaining properties for the render surface, if the layer has one. + if (layer->renderSurface() && layer != rootLayer) { + RenderSurfaceType* renderSurface = layer->renderSurface(); + IntRect clippedContentRect = localDrawableContentRectOfSubtree; + + // The render surface clipRect is expressed in the space where this surface draws, i.e. the same space as clipRectFromAncestor. + if (ancestorClipsSubtree) + renderSurface->setClipRect(clipRectFromAncestor); + else + renderSurface->setClipRect(IntRect()); + + // Don't clip if the layer is reflected as the reflection shouldn't be + // clipped. If the layer is animating, then the surface's transform to + // its target is not known on the main thread, and we should not use it + // to clip. + if (!layer->replicaLayer() && transformToParentIsKnown(layer)) { + // Note, it is correct to use ancestorClipsSubtree here, because we are looking at this layer's renderSurface, not the layer itself. + if (ancestorClipsSubtree && !clippedContentRect.isEmpty()) { + IntRect surfaceClipRect = CCLayerTreeHostCommon::calculateVisibleRect(renderSurface->clipRect(), clippedContentRect, renderSurface->drawTransform()); + clippedContentRect.intersect(surfaceClipRect); + } + } + + // The RenderSurface backing texture cannot exceed the maximum supported + // texture size. + clippedContentRect.setWidth(std::min(clippedContentRect.width(), maxTextureSize)); + clippedContentRect.setHeight(std::min(clippedContentRect.height(), maxTextureSize)); + + if (clippedContentRect.isEmpty()) + renderSurface->clearLayerList(); + + renderSurface->setContentRect(clippedContentRect); + renderSurface->setScreenSpaceTransform(layer->screenSpaceTransform()); + + if (layer->replicaLayer()) { + WebTransformationMatrix surfaceOriginToReplicaOriginTransform; + surfaceOriginToReplicaOriginTransform.scale(deviceScaleFactor); + surfaceOriginToReplicaOriginTransform.translate(layer->replicaLayer()->position().x() + layer->replicaLayer()->anchorPoint().x() * bounds.width(), + layer->replicaLayer()->position().y() + layer->replicaLayer()->anchorPoint().y() * bounds.height()); + surfaceOriginToReplicaOriginTransform.multiply(layer->replicaLayer()->transform()); + surfaceOriginToReplicaOriginTransform.translate(-layer->replicaLayer()->anchorPoint().x() * bounds.width(), -layer->replicaLayer()->anchorPoint().y() * bounds.height()); + surfaceOriginToReplicaOriginTransform.scale(1 / deviceScaleFactor); + + // Compute the replica's "originTransform" that maps from the replica's origin space to the target surface origin space. + WebTransformationMatrix replicaOriginTransform = layer->renderSurface()->drawTransform() * surfaceOriginToReplicaOriginTransform; + renderSurface->setReplicaDrawTransform(replicaOriginTransform); + + // Compute the replica's "screenSpaceTransform" that maps from the replica's origin space to the screen's origin space. + WebTransformationMatrix replicaScreenSpaceTransform = layer->renderSurface()->screenSpaceTransform() * surfaceOriginToReplicaOriginTransform; + renderSurface->setReplicaScreenSpaceTransform(replicaScreenSpaceTransform); + } + + // If a render surface has no layer list, then it and none of its children needed to get drawn. + if (!layer->renderSurface()->layerList().size()) { + // FIXME: Originally we asserted that this layer was already at the end of the + // list, and only needed to remove that layer. For now, we remove the + // entire subtree of surfaces to fix a crash bug. The root cause is + // https://bugs.webkit.org/show_bug.cgi?id=74147 and we should be able + // to put the original assert after fixing that. + while (renderSurfaceLayerList.last() != layer) { + renderSurfaceLayerList.last()->clearRenderSurface(); + renderSurfaceLayerList.removeLast(); + } + ASSERT(renderSurfaceLayerList.last() == layer); + renderSurfaceLayerList.removeLast(); + layer->clearRenderSurface(); + return; + } + } + + // If neither this layer nor any of its children were added, early out. + if (sortingStartIndex == descendants.size()) + return; + + // If preserves-3d then sort all the descendants in 3D so that they can be + // drawn from back to front. If the preserves-3d property is also set on the parent then + // skip the sorting as the parent will sort all the descendants anyway. + if (descendants.size() && layer->preserves3D() && (!layer->parent() || !layer->parent()->preserves3D())) + sortLayers(&descendants.at(sortingStartIndex), descendants.end(), layerSorter); + + if (layer->renderSurface()) + drawableContentRectOfSubtree = enclosingIntRect(layer->renderSurface()->drawableContentRect()); + else + drawableContentRectOfSubtree = localDrawableContentRectOfSubtree; + + return; +} + +// FIXME: Instead of using the following function to set visibility rects on a second +// tree pass, revise calculateVisibleContentRect() so that this can be done in a single +// pass inside calculateDrawTransformsInternal<>(). +template<typename LayerType, typename LayerList, typename RenderSurfaceType> +static void calculateVisibleRectsInternal(const LayerList& renderSurfaceLayerList) +{ + // Use BackToFront since it's cheap and this isn't order-dependent. + typedef CCLayerIterator<LayerType, LayerList, RenderSurfaceType, CCLayerIteratorActions::BackToFront> CCLayerIteratorType; + + CCLayerIteratorType end = CCLayerIteratorType::end(&renderSurfaceLayerList); + for (CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); it != end; ++it) { + if (it.representsTargetRenderSurface()) { + LayerType* maskLayer = it->maskLayer(); + if (maskLayer) + maskLayer->setVisibleContentRect(IntRect(IntPoint(), it->contentBounds())); + LayerType* replicaMaskLayer = it->replicaLayer() ? it->replicaLayer()->maskLayer() : 0; + if (replicaMaskLayer) + replicaMaskLayer->setVisibleContentRect(IntRect(IntPoint(), it->contentBounds())); + } else if (it.representsItself()) { + IntRect visibleContentRect = calculateVisibleContentRect(*it); + it->setVisibleContentRect(visibleContentRect); + } + } +} + +void CCLayerTreeHostCommon::calculateDrawTransforms(LayerChromium* rootLayer, const IntSize& deviceViewportSize, float deviceScaleFactor, int maxTextureSize, Vector<RefPtr<LayerChromium> >& renderSurfaceLayerList) +{ + IntRect totalDrawableContentRect; + WebTransformationMatrix identityMatrix; + WebTransformationMatrix deviceScaleTransform; + deviceScaleTransform.scale(deviceScaleFactor); + + setupRootLayerAndSurfaceForRecursion<LayerChromium, Vector<RefPtr<LayerChromium> > >(rootLayer, renderSurfaceLayerList, deviceViewportSize); + + WebCore::calculateDrawTransformsInternal<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, void>(rootLayer, rootLayer, deviceScaleTransform, identityMatrix, identityMatrix, + rootLayer->renderSurface()->contentRect(), true, 0, renderSurfaceLayerList, + rootLayer->renderSurface()->layerList(), 0, maxTextureSize, deviceScaleFactor, totalDrawableContentRect); +} + +void CCLayerTreeHostCommon::calculateDrawTransforms(CCLayerImpl* rootLayer, const IntSize& deviceViewportSize, float deviceScaleFactor, CCLayerSorter* layerSorter, int maxTextureSize, Vector<CCLayerImpl*>& renderSurfaceLayerList) +{ + IntRect totalDrawableContentRect; + WebTransformationMatrix identityMatrix; + WebTransformationMatrix deviceScaleTransform; + deviceScaleTransform.scale(deviceScaleFactor); + + setupRootLayerAndSurfaceForRecursion<CCLayerImpl, Vector<CCLayerImpl*> >(rootLayer, renderSurfaceLayerList, deviceViewportSize); + + WebCore::calculateDrawTransformsInternal<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, CCLayerSorter>(rootLayer, rootLayer, deviceScaleTransform, identityMatrix, identityMatrix, + rootLayer->renderSurface()->contentRect(), true, 0, renderSurfaceLayerList, + rootLayer->renderSurface()->layerList(), layerSorter, maxTextureSize, deviceScaleFactor, totalDrawableContentRect); +} + +void CCLayerTreeHostCommon::calculateVisibleRects(Vector<RefPtr<LayerChromium> >& renderSurfaceLayerList) +{ + calculateVisibleRectsInternal<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium>(renderSurfaceLayerList); +} + +void CCLayerTreeHostCommon::calculateVisibleRects(Vector<CCLayerImpl*>& renderSurfaceLayerList) +{ + calculateVisibleRectsInternal<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface>(renderSurfaceLayerList); +} + +static bool pointHitsRect(const IntPoint& viewportPoint, const WebTransformationMatrix& localSpaceToScreenSpaceTransform, FloatRect localSpaceRect) +{ + // If the transform is not invertible, then assume that this point doesn't hit this rect. + if (!localSpaceToScreenSpaceTransform.isInvertible()) + return false; + + // Transform the hit test point from screen space to the local space of the given rect. + bool clipped = false; + FloatPoint hitTestPointInLocalSpace = CCMathUtil::projectPoint(localSpaceToScreenSpaceTransform.inverse(), FloatPoint(viewportPoint), clipped); + + // If projectPoint could not project to a valid value, then we assume that this point doesn't hit this rect. + if (clipped) + return false; + + return localSpaceRect.contains(hitTestPointInLocalSpace); +} + +static bool pointIsClippedBySurfaceOrClipRect(const IntPoint& viewportPoint, CCLayerImpl* layer) +{ + CCLayerImpl* currentLayer = layer; + + // Walk up the layer tree and hit-test any renderSurfaces and any layer clipRects that are active. + while (currentLayer) { + if (currentLayer->renderSurface() && !pointHitsRect(viewportPoint, currentLayer->renderSurface()->screenSpaceTransform(), currentLayer->renderSurface()->contentRect())) + return true; + + // Note that drawableContentRects are actually in targetSurface space, so the transform we + // have to provide is the target surface's screenSpaceTransform. + CCLayerImpl* renderTarget = currentLayer->renderTarget(); + if (layerClipsSubtree(currentLayer) && !pointHitsRect(viewportPoint, renderTarget->renderSurface()->screenSpaceTransform(), currentLayer->drawableContentRect())) + return true; + + currentLayer = currentLayer->parent(); + } + + // If we have finished walking all ancestors without having already exited, then the point is not clipped by any ancestors. + return false; +} + +CCLayerImpl* CCLayerTreeHostCommon::findLayerThatIsHitByPoint(const IntPoint& viewportPoint, Vector<CCLayerImpl*>& renderSurfaceLayerList) +{ + CCLayerImpl* foundLayer = 0; + + typedef CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, CCLayerIteratorActions::FrontToBack> CCLayerIteratorType; + CCLayerIteratorType end = CCLayerIteratorType::end(&renderSurfaceLayerList); + + for (CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); it != end; ++it) { + // We don't want to consider renderSurfaces for hit testing. + if (!it.representsItself()) + continue; + + CCLayerImpl* currentLayer = (*it); + + FloatRect contentRect(FloatPoint::zero(), currentLayer->contentBounds()); + if (!pointHitsRect(viewportPoint, currentLayer->screenSpaceTransform(), contentRect)) + continue; + + // At this point, we think the point does hit the layer, but we need to walk up + // the parents to ensure that the layer was not clipped in such a way that the + // hit point actually should not hit the layer. + if (pointIsClippedBySurfaceOrClipRect(viewportPoint, currentLayer)) + continue; + + foundLayer = currentLayer; + break; + } + + // This can potentially return 0, which means the viewportPoint did not successfully hit test any layers, not even the root layer. + return foundLayer; +} + +} // namespace WebCore diff --git a/cc/CCLayerTreeHostCommon.h b/cc/CCLayerTreeHostCommon.h new file mode 100644 index 0000000..b0c56b0 --- /dev/null +++ b/cc/CCLayerTreeHostCommon.h @@ -0,0 +1,85 @@ +// 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. + +#ifndef CCLayerTreeHostCommon_h +#define CCLayerTreeHostCommon_h + +#include "IntRect.h" +#include "IntSize.h" +#include <public/WebTransformationMatrix.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class CCLayerImpl; +class CCLayerSorter; +class LayerChromium; + +class CCLayerTreeHostCommon { +public: + static IntRect calculateVisibleRect(const IntRect& targetSurfaceRect, const IntRect& layerBoundRect, const WebKit::WebTransformationMatrix&); + + static void calculateDrawTransforms(LayerChromium* rootLayer, const IntSize& deviceViewportSize, float deviceScaleFactor, int maxTextureSize, Vector<RefPtr<LayerChromium> >& renderSurfaceLayerList); + static void calculateDrawTransforms(CCLayerImpl* rootLayer, const IntSize& deviceViewportSize, float deviceScaleFactor, CCLayerSorter*, int maxTextureSize, Vector<CCLayerImpl*>& renderSurfaceLayerList); + + static void calculateVisibleRects(Vector<CCLayerImpl*>& renderSurfaceLayerList); + static void calculateVisibleRects(Vector<RefPtr<LayerChromium> >& renderSurfaceLayerList); + + // Performs hit testing for a given renderSurfaceLayerList. + static CCLayerImpl* findLayerThatIsHitByPoint(const IntPoint& viewportPoint, Vector<CCLayerImpl*>& renderSurfaceLayerList); + + template<typename LayerType> static bool renderSurfaceContributesToTarget(LayerType*, int targetSurfaceLayerID); + + // Returns a layer with the given id if one exists in the subtree starting + // from the given root layer (including mask and replica layers). + template<typename LayerType> static LayerType* findLayerInSubtree(LayerType* rootLayer, int layerId); + + struct ScrollUpdateInfo { + int layerId; + IntSize scrollDelta; + }; +}; + +struct CCScrollAndScaleSet { + Vector<CCLayerTreeHostCommon::ScrollUpdateInfo> scrolls; + float pageScaleDelta; +}; + +template<typename LayerType> +bool CCLayerTreeHostCommon::renderSurfaceContributesToTarget(LayerType* layer, int targetSurfaceLayerID) +{ + // A layer will either contribute its own content, or its render surface's content, to + // the target surface. The layer contributes its surface's content when both the + // following are true: + // (1) The layer actually has a renderSurface, and + // (2) The layer's renderSurface is not the same as the targetSurface. + // + // Otherwise, the layer just contributes itself to the target surface. + + return layer->renderSurface() && layer->id() != targetSurfaceLayerID; +} + +template<typename LayerType> +LayerType* CCLayerTreeHostCommon::findLayerInSubtree(LayerType* rootLayer, int layerId) +{ + if (rootLayer->id() == layerId) + return rootLayer; + + if (rootLayer->maskLayer() && rootLayer->maskLayer()->id() == layerId) + return rootLayer->maskLayer(); + + if (rootLayer->replicaLayer() && rootLayer->replicaLayer()->id() == layerId) + return rootLayer->replicaLayer(); + + for (size_t i = 0; i < rootLayer->children().size(); ++i) { + if (LayerType* found = findLayerInSubtree(rootLayer->children()[i].get(), layerId)) + return found; + } + return 0; +} + +} // namespace WebCore + +#endif diff --git a/cc/CCLayerTreeHostCommonTest.cpp b/cc/CCLayerTreeHostCommonTest.cpp new file mode 100644 index 0000000..2fef05d --- /dev/null +++ b/cc/CCLayerTreeHostCommonTest.cpp @@ -0,0 +1,3247 @@ +// 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 "config.h" + +#include "CCLayerTreeHostCommon.h" + +#include "CCAnimationTestCommon.h" +#include "CCLayerAnimationController.h" +#include "CCLayerImpl.h" +#include "CCLayerSorter.h" +#include "CCLayerTreeTestCommon.h" +#include "CCMathUtil.h" +#include "CCProxy.h" +#include "CCSingleThreadProxy.h" +#include "CCThread.h" +#include "ContentLayerChromium.h" +#include "LayerChromium.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/WebTransformationMatrix.h> + +using namespace WebCore; +using namespace WebKitTests; +using WebKit::WebTransformationMatrix; + +void WebKitTests::ExpectTransformationMatrixEq(WebTransformationMatrix expected, + WebTransformationMatrix actual) +{ + EXPECT_FLOAT_EQ((expected).m11(), (actual).m11()); + EXPECT_FLOAT_EQ((expected).m12(), (actual).m12()); + EXPECT_FLOAT_EQ((expected).m13(), (actual).m13()); + EXPECT_FLOAT_EQ((expected).m14(), (actual).m14()); + EXPECT_FLOAT_EQ((expected).m21(), (actual).m21()); + EXPECT_FLOAT_EQ((expected).m22(), (actual).m22()); + EXPECT_FLOAT_EQ((expected).m23(), (actual).m23()); + EXPECT_FLOAT_EQ((expected).m24(), (actual).m24()); + EXPECT_FLOAT_EQ((expected).m31(), (actual).m31()); + EXPECT_FLOAT_EQ((expected).m32(), (actual).m32()); + EXPECT_FLOAT_EQ((expected).m33(), (actual).m33()); + EXPECT_FLOAT_EQ((expected).m34(), (actual).m34()); + EXPECT_FLOAT_EQ((expected).m41(), (actual).m41()); + EXPECT_FLOAT_EQ((expected).m42(), (actual).m42()); + EXPECT_FLOAT_EQ((expected).m43(), (actual).m43()); + EXPECT_FLOAT_EQ((expected).m44(), (actual).m44()); +} + +namespace { + +template<typename LayerType> +void setLayerPropertiesForTesting(LayerType* layer, const WebTransformationMatrix& transform, const WebTransformationMatrix& sublayerTransform, const FloatPoint& anchor, const FloatPoint& position, const IntSize& bounds, bool preserves3D) +{ + layer->setTransform(transform); + layer->setSublayerTransform(sublayerTransform); + layer->setAnchorPoint(anchor); + layer->setPosition(position); + layer->setBounds(bounds); + layer->setPreserves3D(preserves3D); +} + +void setLayerPropertiesForTesting(LayerChromium* layer, const WebTransformationMatrix& transform, const WebTransformationMatrix& sublayerTransform, const FloatPoint& anchor, const FloatPoint& position, const IntSize& bounds, bool preserves3D) +{ + setLayerPropertiesForTesting<LayerChromium>(layer, transform, sublayerTransform, anchor, position, bounds, preserves3D); +} + +void setLayerPropertiesForTesting(CCLayerImpl* layer, const WebTransformationMatrix& transform, const WebTransformationMatrix& sublayerTransform, const FloatPoint& anchor, const FloatPoint& position, const IntSize& bounds, bool preserves3D) +{ + setLayerPropertiesForTesting<CCLayerImpl>(layer, transform, sublayerTransform, anchor, position, bounds, preserves3D); + layer->setContentBounds(bounds); +} + +void executeCalculateDrawTransformsAndVisibility(LayerChromium* rootLayer) +{ + WebTransformationMatrix identityMatrix; + Vector<RefPtr<LayerChromium> > dummyRenderSurfaceLayerList; + int dummyMaxTextureSize = 512; + + // We are probably not testing what is intended if the rootLayer bounds are empty. + ASSERT(!rootLayer->bounds().isEmpty()); + CCLayerTreeHostCommon::calculateDrawTransforms(rootLayer, rootLayer->bounds(), 1, dummyMaxTextureSize, dummyRenderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(dummyRenderSurfaceLayerList); +} + +void executeCalculateDrawTransformsAndVisibility(CCLayerImpl* rootLayer) +{ + // Note: this version skips layer sorting. + + WebTransformationMatrix identityMatrix; + Vector<CCLayerImpl*> dummyRenderSurfaceLayerList; + int dummyMaxTextureSize = 512; + + // We are probably not testing what is intended if the rootLayer bounds are empty. + ASSERT(!rootLayer->bounds().isEmpty()); + CCLayerTreeHostCommon::calculateDrawTransforms(rootLayer, rootLayer->bounds(), 1, 0, dummyMaxTextureSize, dummyRenderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(dummyRenderSurfaceLayerList); +} + +WebTransformationMatrix remove3DComponentOfMatrix(const WebTransformationMatrix& mat) +{ + WebTransformationMatrix ret = mat; + ret.setM13(0); + ret.setM23(0); + ret.setM31(0); + ret.setM32(0); + ret.setM33(1); + ret.setM34(0); + ret.setM43(0); + return ret; +} + +PassOwnPtr<CCLayerImpl> createTreeForFixedPositionTests() +{ + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + OwnPtr<CCLayerImpl> child = CCLayerImpl::create(2); + OwnPtr<CCLayerImpl> grandChild = CCLayerImpl::create(3); + OwnPtr<CCLayerImpl> greatGrandChild = CCLayerImpl::create(4); + + WebTransformationMatrix IdentityMatrix; + FloatPoint anchor(0, 0); + FloatPoint position(0, 0); + IntSize bounds(100, 100); + setLayerPropertiesForTesting(root.get(), IdentityMatrix, IdentityMatrix, anchor, position, bounds, false); + setLayerPropertiesForTesting(child.get(), IdentityMatrix, IdentityMatrix, anchor, position, bounds, false); + setLayerPropertiesForTesting(grandChild.get(), IdentityMatrix, IdentityMatrix, anchor, position, bounds, false); + setLayerPropertiesForTesting(greatGrandChild.get(), IdentityMatrix, IdentityMatrix, anchor, position, bounds, false); + + grandChild->addChild(greatGrandChild.release()); + child->addChild(grandChild.release()); + root->addChild(child.release()); + + return root.release(); +} + +class LayerChromiumWithForcedDrawsContent : public LayerChromium { +public: + LayerChromiumWithForcedDrawsContent() + : LayerChromium() + { + } + + virtual bool drawsContent() const OVERRIDE { return true; } +}; + +TEST(CCLayerTreeHostCommonTest, verifyTransformsForNoOpLayer) +{ + // Sanity check: For layers positioned at zero, with zero size, + // and with identity transforms, then the drawTransform, + // screenSpaceTransform, and the hierarchy passed on to children + // layers should also be identity transforms. + + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromium> grandChild = LayerChromium::create(); + parent->addChild(child); + child->addChild(grandChild); + + WebTransformationMatrix identityMatrix; + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(0, 0), false); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(0, 0), false); + + executeCalculateDrawTransformsAndVisibility(parent.get()); + + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, child->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, grandChild->screenSpaceTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyTransformsForSingleLayer) +{ + WebTransformationMatrix identityMatrix; + RefPtr<LayerChromium> layer = LayerChromium::create(); + + // Case 1: setting the sublayer transform should not affect this layer's draw transform or screen-space transform. + WebTransformationMatrix arbitraryTranslation; + arbitraryTranslation.translate(10, 20); + setLayerPropertiesForTesting(layer.get(), identityMatrix, arbitraryTranslation, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + executeCalculateDrawTransformsAndVisibility(layer.get()); + WebTransformationMatrix expectedDrawTransform = identityMatrix; + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedDrawTransform, layer->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, layer->screenSpaceTransform()); + + // Case 2: Setting the bounds of the layer should not affect either the draw transform or the screenspace transform. + WebTransformationMatrix translationToCenter; + translationToCenter.translate(5, 6); + setLayerPropertiesForTesting(layer.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 12), false); + executeCalculateDrawTransformsAndVisibility(layer.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, layer->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, layer->screenSpaceTransform()); + + // Case 3: The anchor point by itself (without a layer transform) should have no effect on the transforms. + setLayerPropertiesForTesting(layer.get(), identityMatrix, identityMatrix, FloatPoint(0.25, 0.25), FloatPoint(0, 0), IntSize(10, 12), false); + executeCalculateDrawTransformsAndVisibility(layer.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, layer->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, layer->screenSpaceTransform()); + + // Case 4: A change in actual position affects both the draw transform and screen space transform. + WebTransformationMatrix positionTransform; + positionTransform.translate(0, 1.2); + setLayerPropertiesForTesting(layer.get(), identityMatrix, identityMatrix, FloatPoint(0.25, 0.25), FloatPoint(0, 1.2f), IntSize(10, 12), false); + executeCalculateDrawTransformsAndVisibility(layer.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(positionTransform, layer->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(positionTransform, layer->screenSpaceTransform()); + + // Case 5: In the correct sequence of transforms, the layer transform should pre-multiply the translationToCenter. This is easily tested by + // using a scale transform, because scale and translation are not commutative. + WebTransformationMatrix layerTransform; + layerTransform.scale3d(2, 2, 1); + setLayerPropertiesForTesting(layer.get(), layerTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 12), false); + executeCalculateDrawTransformsAndVisibility(layer.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(layerTransform, layer->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(layerTransform, layer->screenSpaceTransform()); + + // Case 6: The layer transform should occur with respect to the anchor point. + WebTransformationMatrix translationToAnchor; + translationToAnchor.translate(5, 0); + WebTransformationMatrix expectedResult = translationToAnchor * layerTransform * translationToAnchor.inverse(); + setLayerPropertiesForTesting(layer.get(), layerTransform, identityMatrix, FloatPoint(0.5, 0), FloatPoint(0, 0), IntSize(10, 12), false); + executeCalculateDrawTransformsAndVisibility(layer.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedResult, layer->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedResult, layer->screenSpaceTransform()); + + // Case 7: Verify that position pre-multiplies the layer transform. + // The current implementation of calculateDrawTransforms does this implicitly, but it is + // still worth testing to detect accidental regressions. + expectedResult = positionTransform * translationToAnchor * layerTransform * translationToAnchor.inverse(); + setLayerPropertiesForTesting(layer.get(), layerTransform, identityMatrix, FloatPoint(0.5, 0), FloatPoint(0, 1.2f), IntSize(10, 12), false); + executeCalculateDrawTransformsAndVisibility(layer.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedResult, layer->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedResult, layer->screenSpaceTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyTransformsForSimpleHierarchy) +{ + WebTransformationMatrix identityMatrix; + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromium> grandChild = LayerChromium::create(); + parent->addChild(child); + child->addChild(grandChild); + + // Case 1: parent's anchorPoint should not affect child or grandChild. + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0.25, 0.25), FloatPoint(0, 0), IntSize(10, 12), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(16, 18), false); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(76, 78), false); + executeCalculateDrawTransformsAndVisibility(parent.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, child->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, grandChild->screenSpaceTransform()); + + // Case 2: parent's position affects child and grandChild. + WebTransformationMatrix parentPositionTransform; + parentPositionTransform.translate(0, 1.2); + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0.25, 0.25), FloatPoint(0, 1.2f), IntSize(10, 12), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(16, 18), false); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(76, 78), false); + executeCalculateDrawTransformsAndVisibility(parent.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentPositionTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentPositionTransform, child->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentPositionTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentPositionTransform, grandChild->screenSpaceTransform()); + + // Case 3: parent's local transform affects child and grandchild + WebTransformationMatrix parentLayerTransform; + parentLayerTransform.scale3d(2, 2, 1); + WebTransformationMatrix parentTranslationToAnchor; + parentTranslationToAnchor.translate(2.5, 3); + WebTransformationMatrix parentCompositeTransform = parentTranslationToAnchor * parentLayerTransform * parentTranslationToAnchor.inverse(); + setLayerPropertiesForTesting(parent.get(), parentLayerTransform, identityMatrix, FloatPoint(0.25, 0.25), FloatPoint(0, 0), IntSize(10, 12), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(16, 18), false); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(76, 78), false); + executeCalculateDrawTransformsAndVisibility(parent.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, child->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, grandChild->screenSpaceTransform()); + + // Case 4: parent's sublayerMatrix affects child and grandchild + // scaling is used here again so that the correct sequence of transforms is properly tested. + // Note that preserves3D is false, but the sublayer matrix should retain its 3D properties when given to child. + // But then, the child also does not preserve3D. When it gives its hierarchy to the grandChild, it should be flattened to 2D. + WebTransformationMatrix parentSublayerMatrix; + parentSublayerMatrix.scale3d(10, 10, 3.3); + WebTransformationMatrix parentTranslationToCenter; + parentTranslationToCenter.translate(5, 6); + // Sublayer matrix is applied to the center of the parent layer. + parentCompositeTransform = parentTranslationToAnchor * parentLayerTransform * parentTranslationToAnchor.inverse() + * parentTranslationToCenter * parentSublayerMatrix * parentTranslationToCenter.inverse(); + WebTransformationMatrix flattenedCompositeTransform = remove3DComponentOfMatrix(parentCompositeTransform); + setLayerPropertiesForTesting(parent.get(), parentLayerTransform, parentSublayerMatrix, FloatPoint(0.25, 0.25), FloatPoint(0, 0), IntSize(10, 12), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(16, 18), false); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(76, 78), false); + executeCalculateDrawTransformsAndVisibility(parent.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, child->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(flattenedCompositeTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(flattenedCompositeTransform, grandChild->screenSpaceTransform()); + + // Case 5: same as Case 4, except that child does preserve 3D, so the grandChild should receive the non-flattened composite transform. + // + setLayerPropertiesForTesting(parent.get(), parentLayerTransform, parentSublayerMatrix, FloatPoint(0.25, 0.25), FloatPoint(0, 0), IntSize(10, 12), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(16, 18), true); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(76, 78), false); + executeCalculateDrawTransformsAndVisibility(parent.get()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, child->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, grandChild->screenSpaceTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyTransformsForSingleRenderSurface) +{ + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> grandChild = adoptRef(new LayerChromiumWithForcedDrawsContent()); + parent->addChild(child); + child->addChild(grandChild); + + // Child is set up so that a new render surface should be created. + child->setOpacity(0.5); + + WebTransformationMatrix identityMatrix; + WebTransformationMatrix parentLayerTransform; + parentLayerTransform.scale3d(1, 0.9, 1); + WebTransformationMatrix parentTranslationToAnchor; + parentTranslationToAnchor.translate(25, 30); + WebTransformationMatrix parentSublayerMatrix; + parentSublayerMatrix.scale3d(0.9, 1, 3.3); + WebTransformationMatrix parentTranslationToCenter; + parentTranslationToCenter.translate(50, 60); + WebTransformationMatrix parentCompositeTransform = parentTranslationToAnchor * parentLayerTransform * parentTranslationToAnchor.inverse() + * parentTranslationToCenter * parentSublayerMatrix * parentTranslationToCenter.inverse(); + + // Child's render surface should not exist yet. + ASSERT_FALSE(child->renderSurface()); + + setLayerPropertiesForTesting(parent.get(), parentLayerTransform, parentSublayerMatrix, FloatPoint(0.25, 0.25), FloatPoint(0, 0), IntSize(100, 120), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(16, 18), false); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(8, 10), false); + executeCalculateDrawTransformsAndVisibility(parent.get()); + + // Render surface should have been created now. + ASSERT_TRUE(child->renderSurface()); + ASSERT_EQ(child, child->renderTarget()); + + // The child layer's draw transform should refer to its new render surface. + // The screen-space transform, however, should still refer to the root. + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, child->screenSpaceTransform()); + + // Because the grandChild is the only drawable content, the child's renderSurface will tighten its bounds to the grandChild. + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, child->renderTarget()->renderSurface()->drawTransform()); + + // The screen space is the same as the target since the child surface draws into the root. + EXPECT_TRANSFORMATION_MATRIX_EQ(parentCompositeTransform, child->renderTarget()->renderSurface()->screenSpaceTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyTransformsForReplica) +{ + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromium> childReplica = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> grandChild = adoptRef(new LayerChromiumWithForcedDrawsContent()); + parent->addChild(child); + child->addChild(grandChild); + child->setReplicaLayer(childReplica.get()); + + // Child is set up so that a new render surface should be created. + child->setOpacity(0.5); + + WebTransformationMatrix identityMatrix; + WebTransformationMatrix parentLayerTransform; + parentLayerTransform.scale3d(2, 2, 1); + WebTransformationMatrix parentTranslationToAnchor; + parentTranslationToAnchor.translate(2.5, 3); + WebTransformationMatrix parentSublayerMatrix; + parentSublayerMatrix.scale3d(10, 10, 3.3); + WebTransformationMatrix parentTranslationToCenter; + parentTranslationToCenter.translate(5, 6); + WebTransformationMatrix parentCompositeTransform = parentTranslationToAnchor * parentLayerTransform * parentTranslationToAnchor.inverse() + * parentTranslationToCenter * parentSublayerMatrix * parentTranslationToCenter.inverse(); + WebTransformationMatrix childTranslationToCenter; + childTranslationToCenter.translate(8, 9); + WebTransformationMatrix replicaLayerTransform; + replicaLayerTransform.scale3d(3, 3, 1); + WebTransformationMatrix replicaCompositeTransform = parentCompositeTransform * replicaLayerTransform; + + // Child's render surface should not exist yet. + ASSERT_FALSE(child->renderSurface()); + + setLayerPropertiesForTesting(parent.get(), parentLayerTransform, parentSublayerMatrix, FloatPoint(0.25, 0.25), FloatPoint(0, 0), IntSize(10, 12), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(16, 18), false); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(-0.5, -0.5), IntSize(1, 1), false); + setLayerPropertiesForTesting(childReplica.get(), replicaLayerTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(0, 0), false); + executeCalculateDrawTransformsAndVisibility(parent.get()); + + // Render surface should have been created now. + ASSERT_TRUE(child->renderSurface()); + ASSERT_EQ(child, child->renderTarget()); + + EXPECT_TRANSFORMATION_MATRIX_EQ(replicaCompositeTransform, child->renderTarget()->renderSurface()->replicaDrawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(replicaCompositeTransform, child->renderTarget()->renderSurface()->replicaScreenSpaceTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyTransformsForRenderSurfaceHierarchy) +{ + // This test creates a more complex tree and verifies it all at once. This covers the following cases: + // - layers that are described w.r.t. a render surface: should have draw transforms described w.r.t. that surface + // - A render surface described w.r.t. an ancestor render surface: should have a draw transform described w.r.t. that ancestor surface + // - Replicas of a render surface are described w.r.t. the replica's transform around its anchor, along with the surface itself. + // - Sanity check on recursion: verify transforms of layers described w.r.t. a render surface that is described w.r.t. an ancestor render surface. + // - verifying that each layer has a reference to the correct renderSurface and renderTarget values. + + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> renderSurface1 = LayerChromium::create(); + RefPtr<LayerChromium> renderSurface2 = LayerChromium::create(); + RefPtr<LayerChromium> childOfRoot = LayerChromium::create(); + RefPtr<LayerChromium> childOfRS1 = LayerChromium::create(); + RefPtr<LayerChromium> childOfRS2 = LayerChromium::create(); + RefPtr<LayerChromium> replicaOfRS1 = LayerChromium::create(); + RefPtr<LayerChromium> replicaOfRS2 = LayerChromium::create(); + RefPtr<LayerChromium> grandChildOfRoot = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> grandChildOfRS1 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> grandChildOfRS2 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + parent->addChild(renderSurface1); + parent->addChild(childOfRoot); + renderSurface1->addChild(childOfRS1); + renderSurface1->addChild(renderSurface2); + renderSurface2->addChild(childOfRS2); + childOfRoot->addChild(grandChildOfRoot); + childOfRS1->addChild(grandChildOfRS1); + childOfRS2->addChild(grandChildOfRS2); + renderSurface1->setReplicaLayer(replicaOfRS1.get()); + renderSurface2->setReplicaLayer(replicaOfRS2.get()); + + // In combination with descendantDrawsContent, opacity != 1 forces the layer to have a new renderSurface. + renderSurface1->setOpacity(0.5); + renderSurface2->setOpacity(0.33f); + + // All layers in the tree are initialized with an anchor at .25 and a size of (10,10). + // matrix "A" is the composite layer transform used in all layers, centered about the anchor point + // matrix "B" is the sublayer transform used in all layers, centered about the center position of the layer. + // matrix "R" is the composite replica transform used in all replica layers. + // + // x component tests that layerTransform and sublayerTransform are done in the right order (translation and scale are noncommutative). + // y component has a translation by 1 for every ancestor, which indicates the "depth" of the layer in the hierarchy. + WebTransformationMatrix translationToAnchor; + translationToAnchor.translate(2.5, 0); + WebTransformationMatrix translationToCenter; + translationToCenter.translate(5, 5); + WebTransformationMatrix layerTransform; + layerTransform.translate(1, 1); + WebTransformationMatrix sublayerTransform; + sublayerTransform.scale3d(10, 1, 1); + WebTransformationMatrix replicaLayerTransform; + replicaLayerTransform.scale3d(-2, 5, 1); + + WebTransformationMatrix A = translationToAnchor * layerTransform * translationToAnchor.inverse(); + WebTransformationMatrix B = translationToCenter * sublayerTransform * translationToCenter.inverse(); + WebTransformationMatrix R = A * translationToAnchor * replicaLayerTransform * translationToAnchor.inverse(); + WebTransformationMatrix identityMatrix; + + setLayerPropertiesForTesting(parent.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(renderSurface1.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(renderSurface2.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(childOfRoot.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(childOfRS1.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(childOfRS2.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChildOfRoot.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChildOfRS1.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChildOfRS2.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(replicaOfRS1.get(), replicaLayerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(), false); + setLayerPropertiesForTesting(replicaOfRS2.get(), replicaLayerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(0, 0), IntSize(), false); + + executeCalculateDrawTransformsAndVisibility(parent.get()); + + // Only layers that are associated with render surfaces should have an actual renderSurface() value. + // + ASSERT_TRUE(parent->renderSurface()); + ASSERT_FALSE(childOfRoot->renderSurface()); + ASSERT_FALSE(grandChildOfRoot->renderSurface()); + + ASSERT_TRUE(renderSurface1->renderSurface()); + ASSERT_FALSE(childOfRS1->renderSurface()); + ASSERT_FALSE(grandChildOfRS1->renderSurface()); + + ASSERT_TRUE(renderSurface2->renderSurface()); + ASSERT_FALSE(childOfRS2->renderSurface()); + ASSERT_FALSE(grandChildOfRS2->renderSurface()); + + // Verify all renderTarget accessors + // + EXPECT_EQ(parent, parent->renderTarget()); + EXPECT_EQ(parent, childOfRoot->renderTarget()); + EXPECT_EQ(parent, grandChildOfRoot->renderTarget()); + + EXPECT_EQ(renderSurface1, renderSurface1->renderTarget()); + EXPECT_EQ(renderSurface1, childOfRS1->renderTarget()); + EXPECT_EQ(renderSurface1, grandChildOfRS1->renderTarget()); + + EXPECT_EQ(renderSurface2, renderSurface2->renderTarget()); + EXPECT_EQ(renderSurface2, childOfRS2->renderTarget()); + EXPECT_EQ(renderSurface2, grandChildOfRS2->renderTarget()); + + // Verify layer draw transforms + // note that draw transforms are described with respect to the nearest ancestor render surface + // but screen space transforms are described with respect to the root. + // + EXPECT_TRANSFORMATION_MATRIX_EQ(A, parent->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A, childOfRoot->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A, grandChildOfRoot->drawTransform()); + + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, renderSurface1->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(B * A, childOfRS1->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(B * A * B * A, grandChildOfRS1->drawTransform()); + + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, renderSurface2->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(B * A, childOfRS2->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(B * A * B * A, grandChildOfRS2->drawTransform()); + + // Verify layer screen-space transforms + // + EXPECT_TRANSFORMATION_MATRIX_EQ(A, parent->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A, childOfRoot->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A, grandChildOfRoot->screenSpaceTransform()); + + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A, renderSurface1->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A, childOfRS1->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A * B * A, grandChildOfRS1->screenSpaceTransform()); + + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A, renderSurface2->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A * B * A, childOfRS2->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A * B * A * B * A, grandChildOfRS2->screenSpaceTransform()); + + // Verify render surface transforms. + // + // Draw transform of render surface 1 is described with respect to root. + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A, renderSurface1->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * R, renderSurface1->renderSurface()->replicaDrawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A, renderSurface1->renderSurface()->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * R, renderSurface1->renderSurface()->replicaScreenSpaceTransform()); + // Draw transform of render surface 2 is described with respect to render surface 2. + EXPECT_TRANSFORMATION_MATRIX_EQ(B * A, renderSurface2->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(B * R, renderSurface2->renderSurface()->replicaDrawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * A, renderSurface2->renderSurface()->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(A * B * A * B * R, renderSurface2->renderSurface()->replicaScreenSpaceTransform()); + + // Sanity check. If these fail there is probably a bug in the test itself. + // It is expected that we correctly set up transforms so that the y-component of the screen-space transform + // encodes the "depth" of the layer in the tree. + EXPECT_FLOAT_EQ(1, parent->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(2, childOfRoot->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(3, grandChildOfRoot->screenSpaceTransform().m42()); + + EXPECT_FLOAT_EQ(2, renderSurface1->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(3, childOfRS1->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(4, grandChildOfRS1->screenSpaceTransform().m42()); + + EXPECT_FLOAT_EQ(3, renderSurface2->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(4, childOfRS2->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(5, grandChildOfRS2->screenSpaceTransform().m42()); +} + +TEST(CCLayerTreeHostCommonTest, verifyTransformsForFlatteningLayer) +{ + // For layers that flatten their subtree, there should be an orthographic projection + // (for x and y values) in the middle of the transform sequence. Note that the way the + // code is currently implemented, it is not expected to use a canonical orthographic + // projection. + + RefPtr<LayerChromium> root = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> grandChild = adoptRef(new LayerChromiumWithForcedDrawsContent()); + + WebTransformationMatrix rotationAboutYAxis; + rotationAboutYAxis.rotate3d(0, 30, 0); + + const WebTransformationMatrix identityMatrix; + setLayerPropertiesForTesting(root.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(100, 100), false); + setLayerPropertiesForTesting(child.get(), rotationAboutYAxis, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChild.get(), rotationAboutYAxis, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 10), false); + + root->addChild(child); + child->addChild(grandChild); + child->setForceRenderSurface(true); + + // No layers in this test should preserve 3d. + ASSERT_FALSE(root->preserves3D()); + ASSERT_FALSE(child->preserves3D()); + ASSERT_FALSE(grandChild->preserves3D()); + + WebTransformationMatrix expectedChildDrawTransform = rotationAboutYAxis; + WebTransformationMatrix expectedChildScreenSpaceTransform = rotationAboutYAxis; + WebTransformationMatrix expectedGrandChildDrawTransform = rotationAboutYAxis; // draws onto child's renderSurface + WebTransformationMatrix expectedGrandChildScreenSpaceTransform = rotationAboutYAxis.to2dTransform() * rotationAboutYAxis; + + executeCalculateDrawTransformsAndVisibility(root.get()); + + // The child's drawTransform should have been taken by its surface. + ASSERT_TRUE(child->renderSurface()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildDrawTransform, child->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildScreenSpaceTransform, child->renderSurface()->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildScreenSpaceTransform, child->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildDrawTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildScreenSpaceTransform, grandChild->screenSpaceTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyTransformsForDegenerateIntermediateLayer) +{ + // A layer that is empty in one axis, but not the other, was accidentally skipping a necessary translation. + // Without that translation, the coordinate space of the layer's drawTransform is incorrect. + // + // Normally this isn't a problem, because the layer wouldn't be drawn anyway, but if that layer becomes a renderSurface, then + // its drawTransform is implicitly inherited by the rest of the subtree, which then is positioned incorrectly as a result. + + RefPtr<LayerChromium> root = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> grandChild = adoptRef(new LayerChromiumWithForcedDrawsContent()); + + // The child height is zero, but has non-zero width that should be accounted for while computing drawTransforms. + const WebTransformationMatrix identityMatrix; + setLayerPropertiesForTesting(root.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(100, 100), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 0), false); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 10), false); + + root->addChild(child); + child->addChild(grandChild); + child->setForceRenderSurface(true); + + executeCalculateDrawTransformsAndVisibility(root.get()); + + ASSERT_TRUE(child->renderSurface()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, child->renderSurface()->drawTransform()); // This is the real test, the rest are sanity checks. + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(identityMatrix, grandChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyRenderSurfaceListForRenderSurfaceWithClippedLayer) +{ + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> renderSurface1 = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> child = adoptRef(new LayerChromiumWithForcedDrawsContent()); + + const WebTransformationMatrix identityMatrix; + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 10), false); + setLayerPropertiesForTesting(renderSurface1.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 10), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint(30, 30), IntSize(10, 10), false); + + parent->addChild(renderSurface1); + renderSurface1->addChild(child); + renderSurface1->setForceRenderSurface(true); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // The child layer's content is entirely outside the parent's clip rect, so the intermediate + // render surface should not be listed here, even if it was forced to be created. Render surfaces without children or visible + // content are unexpected at draw time (e.g. we might try to create a content texture of size 0). + ASSERT_TRUE(parent->renderSurface()); + ASSERT_FALSE(renderSurface1->renderSurface()); + EXPECT_EQ(1U, renderSurfaceLayerList.size()); +} + +TEST(CCLayerTreeHostCommonTest, verifyRenderSurfaceListForTransparentChild) +{ + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> renderSurface1 = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> child = adoptRef(new LayerChromiumWithForcedDrawsContent()); + + const WebTransformationMatrix identityMatrix; + setLayerPropertiesForTesting(renderSurface1.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 10), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 10), false); + + parent->addChild(renderSurface1); + renderSurface1->addChild(child); + renderSurface1->setForceRenderSurface(true); + renderSurface1->setOpacity(0); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Since the layer is transparent, renderSurface1->renderSurface() should not have gotten added anywhere. + // Also, the drawable content rect should not have been extended by the children. + ASSERT_TRUE(parent->renderSurface()); + EXPECT_EQ(0U, parent->renderSurface()->layerList().size()); + EXPECT_EQ(1U, renderSurfaceLayerList.size()); + EXPECT_EQ(parent->id(), renderSurfaceLayerList[0]->id()); + EXPECT_EQ(IntRect(), parent->drawableContentRect()); +} + +TEST(CCLayerTreeHostCommonTest, verifyForceRenderSurface) +{ + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> renderSurface1 = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> child = adoptRef(new LayerChromiumWithForcedDrawsContent()); + renderSurface1->setForceRenderSurface(true); + + const WebTransformationMatrix identityMatrix; + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 10), false); + setLayerPropertiesForTesting(renderSurface1.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 10), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint::zero(), FloatPoint::zero(), IntSize(10, 10), false); + + parent->addChild(renderSurface1); + renderSurface1->addChild(child); + + // Sanity check before the actual test + EXPECT_FALSE(parent->renderSurface()); + EXPECT_FALSE(renderSurface1->renderSurface()); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + + // The root layer always creates a renderSurface + EXPECT_TRUE(parent->renderSurface()); + EXPECT_TRUE(renderSurface1->renderSurface()); + EXPECT_EQ(2U, renderSurfaceLayerList.size()); + + renderSurfaceLayerList.clear(); + renderSurface1->setForceRenderSurface(false); + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + EXPECT_TRUE(parent->renderSurface()); + EXPECT_FALSE(renderSurface1->renderSurface()); + EXPECT_EQ(1U, renderSurfaceLayerList.size()); +} + +TEST(CCLayerTreeHostCommonTest, verifyScrollCompensationForFixedPositionLayerWithDirectContainer) +{ + // This test checks for correct scroll compensation when the fixed-position container + // is the direct parent of the fixed-position layer. + + DebugScopedSetImplThread scopedImplThread; + OwnPtr<CCLayerImpl> root = createTreeForFixedPositionTests(); + CCLayerImpl* child = root->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + + child->setIsContainerForFixedPositionLayers(true); + grandChild->setFixedToContainerLayer(true); + + // Case 1: scrollDelta of 0, 0 + child->setScrollDelta(IntSize(0, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + WebTransformationMatrix expectedChildTransform; + WebTransformationMatrix expectedGrandChildTransform = expectedChildTransform; + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + + // Case 2: scrollDelta of 10, 10 + child->setScrollDelta(IntSize(10, 10)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + // Here the child is affected by scrollDelta, but the fixed position grandChild should not be affected. + expectedChildTransform.makeIdentity(); + expectedChildTransform.translate(-10, -10); + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyScrollCompensationForFixedPositionLayerWithTransformedDirectContainer) +{ + // This test checks for correct scroll compensation when the fixed-position container + // is the direct parent of the fixed-position layer, but that container is transformed. + // In this case, the fixed position element inherits the container's transform, + // but the scrollDelta that has to be undone should not be affected by that transform. + // + // Transforms are in general non-commutative; using something like a non-uniform scale + // helps to verify that translations and non-uniform scales are applied in the correct + // order. + + DebugScopedSetImplThread scopedImplThread; + OwnPtr<CCLayerImpl> root = createTreeForFixedPositionTests(); + CCLayerImpl* child = root->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + + // This scale will cause child and grandChild to be effectively 200 x 800 with respect to the renderTarget. + WebTransformationMatrix nonUniformScale; + nonUniformScale.scaleNonUniform(2, 8); + child->setTransform(nonUniformScale); + + child->setIsContainerForFixedPositionLayers(true); + grandChild->setFixedToContainerLayer(true); + + // Case 1: scrollDelta of 0, 0 + child->setScrollDelta(IntSize(0, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + WebTransformationMatrix expectedChildTransform; + expectedChildTransform.multiply(nonUniformScale); + + WebTransformationMatrix expectedGrandChildTransform = expectedChildTransform; + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + + // Case 2: scrollDelta of 10, 20 + child->setScrollDelta(IntSize(10, 20)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + // The child should be affected by scrollDelta, but the fixed position grandChild should not be affected. + expectedChildTransform.makeIdentity(); + expectedChildTransform.translate(-10, -20); // scrollDelta + expectedChildTransform.multiply(nonUniformScale); + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyScrollCompensationForFixedPositionLayerWithDistantContainer) +{ + // This test checks for correct scroll compensation when the fixed-position container + // is NOT the direct parent of the fixed-position layer. + DebugScopedSetImplThread scopedImplThread; + + OwnPtr<CCLayerImpl> root = createTreeForFixedPositionTests(); + CCLayerImpl* child = root->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + CCLayerImpl* greatGrandChild = grandChild->children()[0].get(); + + child->setIsContainerForFixedPositionLayers(true); + grandChild->setPosition(FloatPoint(8, 6)); + greatGrandChild->setFixedToContainerLayer(true); + + // Case 1: scrollDelta of 0, 0 + child->setScrollDelta(IntSize(0, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + WebTransformationMatrix expectedChildTransform; + WebTransformationMatrix expectedGrandChildTransform; + expectedGrandChildTransform.translate(8, 6); + + WebTransformationMatrix expectedGreatGrandChildTransform = expectedGrandChildTransform; + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildTransform, greatGrandChild->drawTransform()); + + // Case 2: scrollDelta of 10, 10 + child->setScrollDelta(IntSize(10, 10)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + // Here the child and grandChild are affected by scrollDelta, but the fixed position greatGrandChild should not be affected. + expectedChildTransform.makeIdentity(); + expectedChildTransform.translate(-10, -10); + expectedGrandChildTransform.makeIdentity(); + expectedGrandChildTransform.translate(-2, -4); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildTransform, greatGrandChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyScrollCompensationForFixedPositionLayerWithDistantContainerAndTransforms) +{ + // This test checks for correct scroll compensation when the fixed-position container + // is NOT the direct parent of the fixed-position layer, and the hierarchy has various + // transforms that have to be processed in the correct order. + DebugScopedSetImplThread scopedImplThread; + + OwnPtr<CCLayerImpl> root = createTreeForFixedPositionTests(); + CCLayerImpl* child = root->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + CCLayerImpl* greatGrandChild = grandChild->children()[0].get(); + + WebTransformationMatrix rotationAboutZ; + rotationAboutZ.rotate3d(0, 0, 90); + + child->setIsContainerForFixedPositionLayers(true); + child->setTransform(rotationAboutZ); + grandChild->setPosition(FloatPoint(8, 6)); + grandChild->setTransform(rotationAboutZ); + greatGrandChild->setFixedToContainerLayer(true); // greatGrandChild is positioned upside-down with respect to the renderTarget. + + // Case 1: scrollDelta of 0, 0 + child->setScrollDelta(IntSize(0, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + WebTransformationMatrix expectedChildTransform; + expectedChildTransform.multiply(rotationAboutZ); + + WebTransformationMatrix expectedGrandChildTransform; + expectedGrandChildTransform.multiply(rotationAboutZ); // child's local transform is inherited + expectedGrandChildTransform.translate(8, 6); // translation because of position occurs before layer's local transform. + expectedGrandChildTransform.multiply(rotationAboutZ); // grandChild's local transform + + WebTransformationMatrix expectedGreatGrandChildTransform = expectedGrandChildTransform; + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildTransform, greatGrandChild->drawTransform()); + + // Case 2: scrollDelta of 10, 20 + child->setScrollDelta(IntSize(10, 20)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + // Here the child and grandChild are affected by scrollDelta, but the fixed position greatGrandChild should not be affected. + expectedChildTransform.makeIdentity(); + expectedChildTransform.translate(-10, -20); // scrollDelta + expectedChildTransform.multiply(rotationAboutZ); + + expectedGrandChildTransform.makeIdentity(); + expectedGrandChildTransform.translate(-10, -20); // child's scrollDelta is inherited + expectedGrandChildTransform.multiply(rotationAboutZ); // child's local transform is inherited + expectedGrandChildTransform.translate(8, 6); // translation because of position occurs before layer's local transform. + expectedGrandChildTransform.multiply(rotationAboutZ); // grandChild's local transform + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildTransform, greatGrandChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyScrollCompensationForFixedPositionLayerWithMultipleScrollDeltas) +{ + // This test checks for correct scroll compensation when the fixed-position container + // has multiple ancestors that have nonzero scrollDelta before reaching the space where the layer is fixed. + // In this test, each scrollDelta occurs in a different space because of each layer's local transform. + // This test checks for correct scroll compensation when the fixed-position container + // is NOT the direct parent of the fixed-position layer, and the hierarchy has various + // transforms that have to be processed in the correct order. + DebugScopedSetImplThread scopedImplThread; + + OwnPtr<CCLayerImpl> root = createTreeForFixedPositionTests(); + CCLayerImpl* child = root->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + CCLayerImpl* greatGrandChild = grandChild->children()[0].get(); + + WebTransformationMatrix rotationAboutZ; + rotationAboutZ.rotate3d(0, 0, 90); + + child->setIsContainerForFixedPositionLayers(true); + child->setTransform(rotationAboutZ); + grandChild->setPosition(FloatPoint(8, 6)); + grandChild->setTransform(rotationAboutZ); + greatGrandChild->setFixedToContainerLayer(true); // greatGrandChild is positioned upside-down with respect to the renderTarget. + + // Case 1: scrollDelta of 0, 0 + child->setScrollDelta(IntSize(0, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + WebTransformationMatrix expectedChildTransform; + expectedChildTransform.multiply(rotationAboutZ); + + WebTransformationMatrix expectedGrandChildTransform; + expectedGrandChildTransform.multiply(rotationAboutZ); // child's local transform is inherited + expectedGrandChildTransform.translate(8, 6); // translation because of position occurs before layer's local transform. + expectedGrandChildTransform.multiply(rotationAboutZ); // grandChild's local transform + + WebTransformationMatrix expectedGreatGrandChildTransform = expectedGrandChildTransform; + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildTransform, greatGrandChild->drawTransform()); + + // Case 2: scrollDelta of 10, 20 + child->setScrollDelta(IntSize(10, 0)); + grandChild->setScrollDelta(IntSize(5, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + // Here the child and grandChild are affected by scrollDelta, but the fixed position greatGrandChild should not be affected. + expectedChildTransform.makeIdentity(); + expectedChildTransform.translate(-10, 0); // scrollDelta + expectedChildTransform.multiply(rotationAboutZ); + + expectedGrandChildTransform.makeIdentity(); + expectedGrandChildTransform.translate(-10, 0); // child's scrollDelta is inherited + expectedGrandChildTransform.multiply(rotationAboutZ); // child's local transform is inherited + expectedGrandChildTransform.translate(-5, 0); // grandChild's scrollDelta + expectedGrandChildTransform.translate(8, 6); // translation because of position occurs before layer's local transform. + expectedGrandChildTransform.multiply(rotationAboutZ); // grandChild's local transform + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildTransform, greatGrandChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyScrollCompensationForFixedPositionLayerWithIntermediateSurfaceAndTransforms) +{ + // This test checks for correct scroll compensation when the fixed-position container + // contributes to a different renderSurface than the fixed-position layer. In this + // case, the surface drawTransforms also have to be accounted for when checking the + // scrollDelta. + DebugScopedSetImplThread scopedImplThread; + + OwnPtr<CCLayerImpl> root = createTreeForFixedPositionTests(); + CCLayerImpl* child = root->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + CCLayerImpl* greatGrandChild = grandChild->children()[0].get(); + + child->setIsContainerForFixedPositionLayers(true); + grandChild->setPosition(FloatPoint(8, 6)); + grandChild->setForceRenderSurface(true); + greatGrandChild->setFixedToContainerLayer(true); + greatGrandChild->setDrawsContent(true); + + WebTransformationMatrix rotationAboutZ; + rotationAboutZ.rotate3d(0, 0, 90); + grandChild->setTransform(rotationAboutZ); + + // Case 1: scrollDelta of 0, 0 + child->setScrollDelta(IntSize(0, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + WebTransformationMatrix expectedChildTransform; + WebTransformationMatrix expectedSurfaceDrawTransform; + expectedSurfaceDrawTransform.translate(8, 6); + expectedSurfaceDrawTransform.multiply(rotationAboutZ); + WebTransformationMatrix expectedGrandChildTransform; + WebTransformationMatrix expectedGreatGrandChildTransform; + ASSERT_TRUE(grandChild->renderSurface()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedSurfaceDrawTransform, grandChild->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildTransform, greatGrandChild->drawTransform()); + + // Case 2: scrollDelta of 10, 30 + child->setScrollDelta(IntSize(10, 30)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + // Here the grandChild remains unchanged, because it scrolls along with the + // renderSurface, and the translation is actually in the renderSurface. But, the fixed + // position greatGrandChild is more awkward: its actually being drawn with respect to + // the renderSurface, but it needs to remain fixed with resepct to a container beyond + // that surface. So, the net result is that, unlike previous tests where the fixed + // position layer's transform remains unchanged, here the fixed position layer's + // transform explicitly contains the translation that cancels out the scroll. + expectedChildTransform.makeIdentity(); + expectedChildTransform.translate(-10, -30); // scrollDelta + + expectedSurfaceDrawTransform.makeIdentity(); + expectedSurfaceDrawTransform.translate(-10, -30); // scrollDelta + expectedSurfaceDrawTransform.translate(8, 6); + expectedSurfaceDrawTransform.multiply(rotationAboutZ); + + // The rotation and its inverse are needed to place the scrollDelta compensation in + // the correct space. This test will fail if the rotation/inverse are backwards, too, + // so it requires perfect order of operations. + expectedGreatGrandChildTransform.makeIdentity(); + expectedGreatGrandChildTransform.multiply(rotationAboutZ.inverse()); + expectedGreatGrandChildTransform.translate(10, 30); // explicit canceling out the scrollDelta that gets embedded in the fixed position layer's surface. + expectedGreatGrandChildTransform.multiply(rotationAboutZ); + + ASSERT_TRUE(grandChild->renderSurface()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedSurfaceDrawTransform, grandChild->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildTransform, greatGrandChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyScrollCompensationForFixedPositionLayerWithMultipleIntermediateSurfaces) +{ + // This test checks for correct scroll compensation when the fixed-position container + // contributes to a different renderSurface than the fixed-position layer, with + // additional renderSurfaces in-between. This checks that the conversion to ancestor + // surfaces is accumulated properly in the final matrix transform. + DebugScopedSetImplThread scopedImplThread; + + OwnPtr<CCLayerImpl> root = createTreeForFixedPositionTests(); + CCLayerImpl* child = root->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + CCLayerImpl* greatGrandChild = grandChild->children()[0].get(); + + // Add one more layer to the test tree for this scenario. + { + WebTransformationMatrix identity; + OwnPtr<CCLayerImpl> fixedPositionChild = CCLayerImpl::create(5); + setLayerPropertiesForTesting(fixedPositionChild.get(), identity, identity, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + greatGrandChild->addChild(fixedPositionChild.release()); + } + CCLayerImpl* fixedPositionChild = greatGrandChild->children()[0].get(); + + // Actually set up the scenario here. + child->setIsContainerForFixedPositionLayers(true); + grandChild->setPosition(FloatPoint(8, 6)); + grandChild->setForceRenderSurface(true); + greatGrandChild->setPosition(FloatPoint(40, 60)); + greatGrandChild->setForceRenderSurface(true); + fixedPositionChild->setFixedToContainerLayer(true); + fixedPositionChild->setDrawsContent(true); + + // The additional rotations, which are non-commutative with translations, help to + // verify that we have correct order-of-operations in the final scroll compensation. + // Note that rotating about the center of the layer ensures we do not accidentally + // clip away layers that we want to test. + WebTransformationMatrix rotationAboutZ; + rotationAboutZ.translate(50, 50); + rotationAboutZ.rotate3d(0, 0, 90); + rotationAboutZ.translate(-50, -50); + grandChild->setTransform(rotationAboutZ); + greatGrandChild->setTransform(rotationAboutZ); + + // Case 1: scrollDelta of 0, 0 + child->setScrollDelta(IntSize(0, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + WebTransformationMatrix expectedChildTransform; + + WebTransformationMatrix expectedGrandChildSurfaceDrawTransform; + expectedGrandChildSurfaceDrawTransform.translate(8, 6); + expectedGrandChildSurfaceDrawTransform.multiply(rotationAboutZ); + + WebTransformationMatrix expectedGrandChildTransform; + + WebTransformationMatrix expectedGreatGrandChildSurfaceDrawTransform; + expectedGreatGrandChildSurfaceDrawTransform.translate(40, 60); + expectedGreatGrandChildSurfaceDrawTransform.multiply(rotationAboutZ); + + WebTransformationMatrix expectedGreatGrandChildTransform; + + WebTransformationMatrix expectedFixedPositionChildTransform; + + ASSERT_TRUE(grandChild->renderSurface()); + ASSERT_TRUE(greatGrandChild->renderSurface()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildSurfaceDrawTransform, grandChild->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildSurfaceDrawTransform, greatGrandChild->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildTransform, greatGrandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedFixedPositionChildTransform, fixedPositionChild->drawTransform()); + + // Case 2: scrollDelta of 10, 30 + child->setScrollDelta(IntSize(10, 30)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + expectedChildTransform.makeIdentity(); + expectedChildTransform.translate(-10, -30); // scrollDelta + + expectedGrandChildSurfaceDrawTransform.makeIdentity(); + expectedGrandChildSurfaceDrawTransform.translate(-10, -30); // scrollDelta + expectedGrandChildSurfaceDrawTransform.translate(8, 6); + expectedGrandChildSurfaceDrawTransform.multiply(rotationAboutZ); + + // grandChild, greatGrandChild, and greatGrandChild's surface are not expected to + // change, since they are all not fixed, and they are all drawn with respect to + // grandChild's surface that already has the scrollDelta accounted for. + + // But the great-great grandchild, "fixedPositionChild", should have a transform that explicitly cancels out the scrollDelta. + // The expected transform is: + // compoundDrawTransform.inverse() * translate(positive scrollDelta) * compoundOriginTransform + WebTransformationMatrix compoundDrawTransform; // transform from greatGrandChildSurface's origin to the root surface. + compoundDrawTransform.translate(8, 6); // origin translation of grandChild + compoundDrawTransform.multiply(rotationAboutZ); // rotation of grandChild + compoundDrawTransform.translate(40, 60); // origin translation of greatGrandChild + compoundDrawTransform.multiply(rotationAboutZ); // rotation of greatGrandChild + + expectedFixedPositionChildTransform.makeIdentity(); + expectedFixedPositionChildTransform.multiply(compoundDrawTransform.inverse()); + expectedFixedPositionChildTransform.translate(10, 30); // explicit canceling out the scrollDelta that gets embedded in the fixed position layer's surface. + expectedFixedPositionChildTransform.multiply(compoundDrawTransform); + + ASSERT_TRUE(grandChild->renderSurface()); + ASSERT_TRUE(greatGrandChild->renderSurface()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildSurfaceDrawTransform, grandChild->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildSurfaceDrawTransform, greatGrandChild->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGreatGrandChildTransform, greatGrandChild->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedFixedPositionChildTransform, fixedPositionChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyScrollCompensationForFixedPositionLayerWithContainerLayerThatHasSurface) +{ + // This test checks for correct scroll compensation when the fixed-position container + // itself has a renderSurface. In this case, the container layer should be treated + // like a layer that contributes to a renderTarget, and that renderTarget + // is completely irrelevant; it should not affect the scroll compensation. + DebugScopedSetImplThread scopedImplThread; + + OwnPtr<CCLayerImpl> root = createTreeForFixedPositionTests(); + CCLayerImpl* child = root->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + + child->setIsContainerForFixedPositionLayers(true); + child->setForceRenderSurface(true); + grandChild->setFixedToContainerLayer(true); + grandChild->setDrawsContent(true); + + // Case 1: scrollDelta of 0, 0 + child->setScrollDelta(IntSize(0, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + WebTransformationMatrix expectedSurfaceDrawTransform; + expectedSurfaceDrawTransform.translate(0, 0); + WebTransformationMatrix expectedChildTransform; + WebTransformationMatrix expectedGrandChildTransform; + ASSERT_TRUE(child->renderSurface()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedSurfaceDrawTransform, child->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + + // Case 2: scrollDelta of 10, 10 + child->setScrollDelta(IntSize(10, 10)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + // The surface is translated by scrollDelta, the child transform doesn't change + // because it scrolls along with the surface, but the fixed position grandChild + // needs to compensate for the scroll translation. + expectedSurfaceDrawTransform.makeIdentity(); + expectedSurfaceDrawTransform.translate(-10, -10); + expectedGrandChildTransform.makeIdentity(); + expectedGrandChildTransform.translate(10, 10); + + ASSERT_TRUE(child->renderSurface()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedSurfaceDrawTransform, child->renderSurface()->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyScrollCompensationForFixedPositionLayerThatIsAlsoFixedPositionContainer) +{ + // This test checks the scenario where a fixed-position layer also happens to be a + // container itself for a descendant fixed position layer. In particular, the layer + // should not accidentally be fixed to itself. + DebugScopedSetImplThread scopedImplThread; + + OwnPtr<CCLayerImpl> root = createTreeForFixedPositionTests(); + CCLayerImpl* child = root->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + + child->setIsContainerForFixedPositionLayers(true); + grandChild->setFixedToContainerLayer(true); + + // This should not confuse the grandChild. If correct, the grandChild would still be considered fixed to its container (i.e. "child"). + grandChild->setIsContainerForFixedPositionLayers(true); + + // Case 1: scrollDelta of 0, 0 + child->setScrollDelta(IntSize(0, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + WebTransformationMatrix expectedChildTransform; + WebTransformationMatrix expectedGrandChildTransform; + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + + // Case 2: scrollDelta of 10, 10 + child->setScrollDelta(IntSize(10, 10)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + // Here the child is affected by scrollDelta, but the fixed position grandChild should not be affected. + expectedChildTransform.makeIdentity(); + expectedChildTransform.translate(-10, -10); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyScrollCompensationForFixedPositionLayerThatHasNoContainer) +{ + // This test checks scroll compensation when a fixed-position layer does not find any + // ancestor that is a "containerForFixedPositionLayers". In this situation, the layer should + // be fixed to the viewport -- not the rootLayer, which may have transforms of its own. + DebugScopedSetImplThread scopedImplThread; + + OwnPtr<CCLayerImpl> root = createTreeForFixedPositionTests(); + CCLayerImpl* child = root->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + + WebTransformationMatrix rotationByZ; + rotationByZ.rotate3d(0, 0, 90); + + root->setTransform(rotationByZ); + grandChild->setFixedToContainerLayer(true); + + // Case 1: root scrollDelta of 0, 0 + root->setScrollDelta(IntSize(0, 0)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + WebTransformationMatrix expectedChildTransform; + expectedChildTransform.multiply(rotationByZ); + + WebTransformationMatrix expectedGrandChildTransform; + expectedGrandChildTransform.multiply(rotationByZ); + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); + + // Case 2: root scrollDelta of 10, 10 + root->setScrollDelta(IntSize(10, 10)); + executeCalculateDrawTransformsAndVisibility(root.get()); + + // Here the child is affected by scrollDelta, but the fixed position grandChild should not be affected. + expectedChildTransform.makeIdentity(); + expectedChildTransform.translate(-10, -10); // the scrollDelta + expectedChildTransform.multiply(rotationByZ); + + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedGrandChildTransform, grandChild->drawTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyClipRectCullsRenderSurfaces) +{ + // The entire subtree of layers that are outside the clipRect should be culled away, + // and should not affect the renderSurfaceLayerList. + // + // The test tree is set up as follows: + // - all layers except the leafNodes are forced to be a new renderSurface that have something to draw. + // - parent is a large container layer. + // - child has masksToBounds=true to cause clipping. + // - grandChild is positioned outside of the child's bounds + // - greatGrandChild is also kept outside child's bounds. + // + // In this configuration, grandChild and greatGrandChild are completely outside the + // clipRect, and they should never get scheduled on the list of renderSurfaces. + // + + const WebTransformationMatrix identityMatrix; + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromium> grandChild = LayerChromium::create(); + RefPtr<LayerChromium> greatGrandChild = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> leafNode1 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> leafNode2 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + parent->addChild(child); + child->addChild(grandChild); + grandChild->addChild(greatGrandChild); + + // leafNode1 ensures that parent and child are kept on the renderSurfaceLayerList, + // even though grandChild and greatGrandChild should be clipped. + child->addChild(leafNode1); + greatGrandChild->addChild(leafNode2); + + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(500, 500), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(20, 20), false); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(45, 45), IntSize(10, 10), false); + setLayerPropertiesForTesting(greatGrandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(leafNode1.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(500, 500), false); + setLayerPropertiesForTesting(leafNode2.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(20, 20), false); + + child->setMasksToBounds(true); + child->setOpacity(0.4f); + grandChild->setOpacity(0.5); + greatGrandChild->setOpacity(0.4f); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + + ASSERT_EQ(2U, renderSurfaceLayerList.size()); + EXPECT_EQ(parent->id(), renderSurfaceLayerList[0]->id()); + EXPECT_EQ(child->id(), renderSurfaceLayerList[1]->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyClipRectCullsSurfaceWithoutVisibleContent) +{ + // When a renderSurface has a clipRect, it is used to clip the contentRect + // of the surface. When the renderSurface is animating its transforms, then + // the contentRect's position in the clipRect is not defined on the main + // thread, and its contentRect should not be clipped. + + // The test tree is set up as follows: + // - parent is a container layer that masksToBounds=true to cause clipping. + // - child is a renderSurface, which has a clipRect set to the bounds of the parent. + // - grandChild is a renderSurface, and the only visible content in child. It is positioned outside of the clipRect from parent. + + // In this configuration, grandChild should be outside the clipped + // contentRect of the child, making grandChild not appear in the + // renderSurfaceLayerList. However, when we place an animation on the child, + // this clipping should be avoided and we should keep the grandChild + // in the renderSurfaceLayerList. + + const WebTransformationMatrix identityMatrix; + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromium> grandChild = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> leafNode = adoptRef(new LayerChromiumWithForcedDrawsContent()); + parent->addChild(child); + child->addChild(grandChild); + grandChild->addChild(leafNode); + + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(20, 20), false); + setLayerPropertiesForTesting(grandChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(200, 200), IntSize(10, 10), false); + setLayerPropertiesForTesting(leafNode.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 10), false); + + parent->setMasksToBounds(true); + child->setOpacity(0.4f); + grandChild->setOpacity(0.4f); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + + // Without an animation, we should cull child and grandChild from the renderSurfaceLayerList. + ASSERT_EQ(1U, renderSurfaceLayerList.size()); + EXPECT_EQ(parent->id(), renderSurfaceLayerList[0]->id()); + + // Now put an animating transform on child. + addAnimatedTransformToController(*child->layerAnimationController(), 10, 30, 0); + + parent->clearRenderSurface(); + child->clearRenderSurface(); + grandChild->clearRenderSurface(); + renderSurfaceLayerList.clear(); + + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + + // With an animating transform, we should keep child and grandChild in the renderSurfaceLayerList. + ASSERT_EQ(3U, renderSurfaceLayerList.size()); + EXPECT_EQ(parent->id(), renderSurfaceLayerList[0]->id()); + EXPECT_EQ(child->id(), renderSurfaceLayerList[1]->id()); + EXPECT_EQ(grandChild->id(), renderSurfaceLayerList[2]->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyDrawableContentRectForLayers) +{ + // Verify that layers get the appropriate drawableContentRect when their parent masksToBounds is true. + // + // grandChild1 - completely inside the region; drawableContentRect should be the layer rect expressed in target space. + // grandChild2 - partially clipped but NOT masksToBounds; the clipRect will be the intersection of layerBounds and the mask region. + // grandChild3 - partially clipped and masksToBounds; the drawableContentRect will still be the intersection of layerBounds and the mask region. + // grandChild4 - outside parent's clipRect; the drawableContentRect should be empty. + // + + const WebTransformationMatrix identityMatrix; + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromium> grandChild1 = LayerChromium::create(); + RefPtr<LayerChromium> grandChild2 = LayerChromium::create(); + RefPtr<LayerChromium> grandChild3 = LayerChromium::create(); + RefPtr<LayerChromium> grandChild4 = LayerChromium::create(); + + parent->addChild(child); + child->addChild(grandChild1); + child->addChild(grandChild2); + child->addChild(grandChild3); + child->addChild(grandChild4); + + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(500, 500), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(20, 20), false); + setLayerPropertiesForTesting(grandChild1.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(5, 5), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChild2.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(15, 15), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChild3.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(15, 15), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChild4.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(45, 45), IntSize(10, 10), false); + + child->setMasksToBounds(true); + grandChild3->setMasksToBounds(true); + + // Force everyone to be a render surface. + child->setOpacity(0.4f); + grandChild1->setOpacity(0.5); + grandChild2->setOpacity(0.5); + grandChild3->setOpacity(0.5); + grandChild4->setOpacity(0.5); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + EXPECT_INT_RECT_EQ(IntRect(IntPoint(5, 5), IntSize(10, 10)), grandChild1->drawableContentRect()); + EXPECT_INT_RECT_EQ(IntRect(IntPoint(15, 15), IntSize(5, 5)), grandChild3->drawableContentRect()); + EXPECT_INT_RECT_EQ(IntRect(IntPoint(15, 15), IntSize(5, 5)), grandChild3->drawableContentRect()); + EXPECT_TRUE(grandChild4->drawableContentRect().isEmpty()); +} + +TEST(CCLayerTreeHostCommonTest, verifyClipRectIsPropagatedCorrectlyToSurfaces) +{ + // Verify that renderSurfaces (and their layers) get the appropriate clipRects when their parent masksToBounds is true. + // + // Layers that own renderSurfaces (at least for now) do not inherit any clipping; + // instead the surface will enforce the clip for the entire subtree. They may still + // have a clipRect of their own layer bounds, however, if masksToBounds was true. + // + + const WebTransformationMatrix identityMatrix; + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromium> grandChild1 = LayerChromium::create(); + RefPtr<LayerChromium> grandChild2 = LayerChromium::create(); + RefPtr<LayerChromium> grandChild3 = LayerChromium::create(); + RefPtr<LayerChromium> grandChild4 = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> leafNode1 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> leafNode2 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> leafNode3 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> leafNode4 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + + parent->addChild(child); + child->addChild(grandChild1); + child->addChild(grandChild2); + child->addChild(grandChild3); + child->addChild(grandChild4); + + // the leaf nodes ensure that these grandChildren become renderSurfaces for this test. + grandChild1->addChild(leafNode1); + grandChild2->addChild(leafNode2); + grandChild3->addChild(leafNode3); + grandChild4->addChild(leafNode4); + + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(500, 500), false); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(20, 20), false); + setLayerPropertiesForTesting(grandChild1.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(5, 5), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChild2.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(15, 15), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChild3.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(15, 15), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChild4.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(45, 45), IntSize(10, 10), false); + setLayerPropertiesForTesting(leafNode1.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(leafNode2.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(leafNode3.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(leafNode4.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 10), false); + + child->setMasksToBounds(true); + grandChild3->setMasksToBounds(true); + grandChild4->setMasksToBounds(true); + + // Force everyone to be a render surface. + child->setOpacity(0.4f); + grandChild1->setOpacity(0.5); + grandChild2->setOpacity(0.5); + grandChild3->setOpacity(0.5); + grandChild4->setOpacity(0.5); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + ASSERT_TRUE(grandChild1->renderSurface()); + ASSERT_TRUE(grandChild2->renderSurface()); + ASSERT_TRUE(grandChild3->renderSurface()); + EXPECT_FALSE(grandChild4->renderSurface()); // Because grandChild4 is entirely clipped, it is expected to not have a renderSurface. + + // Surfaces are clipped by their parent, but un-affected by the owning layer's masksToBounds. + EXPECT_INT_RECT_EQ(IntRect(IntPoint(0, 0), IntSize(20, 20)), grandChild1->renderSurface()->clipRect()); + EXPECT_INT_RECT_EQ(IntRect(IntPoint(0, 0), IntSize(20, 20)), grandChild2->renderSurface()->clipRect()); + EXPECT_INT_RECT_EQ(IntRect(IntPoint(0, 0), IntSize(20, 20)), grandChild3->renderSurface()->clipRect()); +} + +TEST(CCLayerTreeHostCommonTest, verifyAnimationsForRenderSurfaceHierarchy) +{ + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromium> renderSurface1 = LayerChromium::create(); + RefPtr<LayerChromium> renderSurface2 = LayerChromium::create(); + RefPtr<LayerChromium> childOfRoot = LayerChromium::create(); + RefPtr<LayerChromium> childOfRS1 = LayerChromium::create(); + RefPtr<LayerChromium> childOfRS2 = LayerChromium::create(); + RefPtr<LayerChromium> grandChildOfRoot = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> grandChildOfRS1 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> grandChildOfRS2 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + parent->addChild(renderSurface1); + parent->addChild(childOfRoot); + renderSurface1->addChild(childOfRS1); + renderSurface1->addChild(renderSurface2); + renderSurface2->addChild(childOfRS2); + childOfRoot->addChild(grandChildOfRoot); + childOfRS1->addChild(grandChildOfRS1); + childOfRS2->addChild(grandChildOfRS2); + + // Make our render surfaces. + renderSurface1->setForceRenderSurface(true); + renderSurface2->setForceRenderSurface(true); + + // Put an animated opacity on the render surface. + addOpacityTransitionToController(*renderSurface1->layerAnimationController(), 10, 1, 0, false); + + // Also put an animated opacity on a layer without descendants. + addOpacityTransitionToController(*grandChildOfRoot->layerAnimationController(), 10, 1, 0, false); + + WebTransformationMatrix layerTransform; + layerTransform.translate(1, 1); + WebTransformationMatrix sublayerTransform; + sublayerTransform.scale3d(10, 1, 1); + + // Put a transform animation on the render surface. + addAnimatedTransformToController(*renderSurface2->layerAnimationController(), 10, 30, 0); + + // Also put transform animations on grandChildOfRoot, and grandChildOfRS2 + addAnimatedTransformToController(*grandChildOfRoot->layerAnimationController(), 10, 30, 0); + addAnimatedTransformToController(*grandChildOfRS2->layerAnimationController(), 10, 30, 0); + + setLayerPropertiesForTesting(parent.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(2.5, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(renderSurface1.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(2.5, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(renderSurface2.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(2.5, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(childOfRoot.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(2.5, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(childOfRS1.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(2.5, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(childOfRS2.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(2.5, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChildOfRoot.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(2.5, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChildOfRS1.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(2.5, 0), IntSize(10, 10), false); + setLayerPropertiesForTesting(grandChildOfRS2.get(), layerTransform, sublayerTransform, FloatPoint(0.25, 0), FloatPoint(2.5, 0), IntSize(10, 10), false); + + executeCalculateDrawTransformsAndVisibility(parent.get()); + + // Only layers that are associated with render surfaces should have an actual renderSurface() value. + // + ASSERT_TRUE(parent->renderSurface()); + ASSERT_FALSE(childOfRoot->renderSurface()); + ASSERT_FALSE(grandChildOfRoot->renderSurface()); + + ASSERT_TRUE(renderSurface1->renderSurface()); + ASSERT_FALSE(childOfRS1->renderSurface()); + ASSERT_FALSE(grandChildOfRS1->renderSurface()); + + ASSERT_TRUE(renderSurface2->renderSurface()); + ASSERT_FALSE(childOfRS2->renderSurface()); + ASSERT_FALSE(grandChildOfRS2->renderSurface()); + + // Verify all renderTarget accessors + // + EXPECT_EQ(parent, parent->renderTarget()); + EXPECT_EQ(parent, childOfRoot->renderTarget()); + EXPECT_EQ(parent, grandChildOfRoot->renderTarget()); + + EXPECT_EQ(renderSurface1, renderSurface1->renderTarget()); + EXPECT_EQ(renderSurface1, childOfRS1->renderTarget()); + EXPECT_EQ(renderSurface1, grandChildOfRS1->renderTarget()); + + EXPECT_EQ(renderSurface2, renderSurface2->renderTarget()); + EXPECT_EQ(renderSurface2, childOfRS2->renderTarget()); + EXPECT_EQ(renderSurface2, grandChildOfRS2->renderTarget()); + + // Verify drawOpacityIsAnimating values + // + EXPECT_FALSE(parent->drawOpacityIsAnimating()); + EXPECT_FALSE(childOfRoot->drawOpacityIsAnimating()); + EXPECT_TRUE(grandChildOfRoot->drawOpacityIsAnimating()); + EXPECT_FALSE(renderSurface1->drawOpacityIsAnimating()); + EXPECT_TRUE(renderSurface1->renderSurface()->drawOpacityIsAnimating()); + EXPECT_FALSE(childOfRS1->drawOpacityIsAnimating()); + EXPECT_FALSE(grandChildOfRS1->drawOpacityIsAnimating()); + EXPECT_FALSE(renderSurface2->drawOpacityIsAnimating()); + EXPECT_FALSE(renderSurface2->renderSurface()->drawOpacityIsAnimating()); + EXPECT_FALSE(childOfRS2->drawOpacityIsAnimating()); + EXPECT_FALSE(grandChildOfRS2->drawOpacityIsAnimating()); + + // Verify drawTransformsAnimatingInTarget values + // + EXPECT_FALSE(parent->drawTransformIsAnimating()); + EXPECT_FALSE(childOfRoot->drawTransformIsAnimating()); + EXPECT_TRUE(grandChildOfRoot->drawTransformIsAnimating()); + EXPECT_FALSE(renderSurface1->drawTransformIsAnimating()); + EXPECT_FALSE(renderSurface1->renderSurface()->targetSurfaceTransformsAreAnimating()); + EXPECT_FALSE(childOfRS1->drawTransformIsAnimating()); + EXPECT_FALSE(grandChildOfRS1->drawTransformIsAnimating()); + EXPECT_FALSE(renderSurface2->drawTransformIsAnimating()); + EXPECT_TRUE(renderSurface2->renderSurface()->targetSurfaceTransformsAreAnimating()); + EXPECT_FALSE(childOfRS2->drawTransformIsAnimating()); + EXPECT_TRUE(grandChildOfRS2->drawTransformIsAnimating()); + + // Verify drawTransformsAnimatingInScreen values + // + EXPECT_FALSE(parent->screenSpaceTransformIsAnimating()); + EXPECT_FALSE(childOfRoot->screenSpaceTransformIsAnimating()); + EXPECT_TRUE(grandChildOfRoot->screenSpaceTransformIsAnimating()); + EXPECT_FALSE(renderSurface1->screenSpaceTransformIsAnimating()); + EXPECT_FALSE(renderSurface1->renderSurface()->screenSpaceTransformsAreAnimating()); + EXPECT_FALSE(childOfRS1->screenSpaceTransformIsAnimating()); + EXPECT_FALSE(grandChildOfRS1->screenSpaceTransformIsAnimating()); + EXPECT_TRUE(renderSurface2->screenSpaceTransformIsAnimating()); + EXPECT_TRUE(renderSurface2->renderSurface()->screenSpaceTransformsAreAnimating()); + EXPECT_TRUE(childOfRS2->screenSpaceTransformIsAnimating()); + EXPECT_TRUE(grandChildOfRS2->screenSpaceTransformIsAnimating()); + + + // Sanity check. If these fail there is probably a bug in the test itself. + // It is expected that we correctly set up transforms so that the y-component of the screen-space transform + // encodes the "depth" of the layer in the tree. + EXPECT_FLOAT_EQ(1, parent->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(2, childOfRoot->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(3, grandChildOfRoot->screenSpaceTransform().m42()); + + EXPECT_FLOAT_EQ(2, renderSurface1->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(3, childOfRS1->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(4, grandChildOfRS1->screenSpaceTransform().m42()); + + EXPECT_FLOAT_EQ(3, renderSurface2->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(4, childOfRS2->screenSpaceTransform().m42()); + EXPECT_FLOAT_EQ(5, grandChildOfRS2->screenSpaceTransform().m42()); +} + +TEST(CCLayerTreeHostCommonTest, verifyVisibleRectForIdentityTransform) +{ + // Test the calculateVisibleRect() function works correctly for identity transforms. + + IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100)); + WebTransformationMatrix layerToSurfaceTransform; + + // Case 1: Layer is contained within the surface. + IntRect layerContentRect = IntRect(IntPoint(10, 10), IntSize(30, 30)); + IntRect expected = IntRect(IntPoint(10, 10), IntSize(30, 30)); + IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); + + // Case 2: Layer is outside the surface rect. + layerContentRect = IntRect(IntPoint(120, 120), IntSize(30, 30)); + actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_TRUE(actual.isEmpty()); + + // Case 3: Layer is partially overlapping the surface rect. + layerContentRect = IntRect(IntPoint(80, 80), IntSize(30, 30)); + expected = IntRect(IntPoint(80, 80), IntSize(20, 20)); + actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); +} + +TEST(CCLayerTreeHostCommonTest, verifyVisibleRectForTranslations) +{ + // Test the calculateVisibleRect() function works correctly for scaling transforms. + + IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100)); + IntRect layerContentRect = IntRect(IntPoint(0, 0), IntSize(30, 30)); + WebTransformationMatrix layerToSurfaceTransform; + + // Case 1: Layer is contained within the surface. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.translate(10, 10); + IntRect expected = IntRect(IntPoint(0, 0), IntSize(30, 30)); + IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); + + // Case 2: Layer is outside the surface rect. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.translate(120, 120); + actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_TRUE(actual.isEmpty()); + + // Case 3: Layer is partially overlapping the surface rect. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.translate(80, 80); + expected = IntRect(IntPoint(0, 0), IntSize(20, 20)); + actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); +} + +TEST(CCLayerTreeHostCommonTest, verifyVisibleRectFor2DRotations) +{ + // Test the calculateVisibleRect() function works correctly for rotations about z-axis (i.e. 2D rotations). + // Remember that calculateVisibleRect() should return the visible rect in the layer's space. + + IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100)); + IntRect layerContentRect = IntRect(IntPoint(0, 0), IntSize(30, 30)); + WebTransformationMatrix layerToSurfaceTransform; + + // Case 1: Layer is contained within the surface. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.translate(50, 50); + layerToSurfaceTransform.rotate(45); + IntRect expected = IntRect(IntPoint(0, 0), IntSize(30, 30)); + IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); + + // Case 2: Layer is outside the surface rect. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.translate(-50, 0); + layerToSurfaceTransform.rotate(45); + actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_TRUE(actual.isEmpty()); + + // Case 3: The layer is rotated about its top-left corner. In surface space, the layer + // is oriented diagonally, with the left half outside of the renderSurface. In + // this case, the visible rect should still be the entire layer (remember the + // visible rect is computed in layer space); both the top-left and + // bottom-right corners of the layer are still visible. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.rotate(45); + expected = IntRect(IntPoint(0, 0), IntSize(30, 30)); + actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); + + // Case 4: The layer is rotated about its top-left corner, and translated upwards. In + // surface space, the layer is oriented diagonally, with only the top corner + // of the surface overlapping the layer. In layer space, the render surface + // overlaps the right side of the layer. The visible rect should be the + // layer's right half. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.translate(0, -sqrt(2.0) * 15); + layerToSurfaceTransform.rotate(45); + expected = IntRect(IntPoint(15, 0), IntSize(15, 30)); // right half of layer bounds. + actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); +} + +TEST(CCLayerTreeHostCommonTest, verifyVisibleRectFor3dOrthographicTransform) +{ + // Test that the calculateVisibleRect() function works correctly for 3d transforms. + + IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100)); + IntRect layerContentRect = IntRect(IntPoint(0, 0), IntSize(100, 100)); + WebTransformationMatrix layerToSurfaceTransform; + + // Case 1: Orthographic projection of a layer rotated about y-axis by 45 degrees, should be fully contained in the renderSurface. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.rotate3d(0, 45, 0); + IntRect expected = IntRect(IntPoint(0, 0), IntSize(100, 100)); + IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); + + // Case 2: Orthographic projection of a layer rotated about y-axis by 45 degrees, but + // shifted to the side so only the right-half the layer would be visible on + // the surface. + double halfWidthOfRotatedLayer = (100 / sqrt(2.0)) * 0.5; // 100 is the un-rotated layer width; divided by sqrt(2) is the rotated width. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.translate(-halfWidthOfRotatedLayer, 0); + layerToSurfaceTransform.rotate3d(0, 45, 0); // rotates about the left edge of the layer + expected = IntRect(IntPoint(50, 0), IntSize(50, 100)); // right half of the layer. + actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); +} + +TEST(CCLayerTreeHostCommonTest, verifyVisibleRectFor3dPerspectiveTransform) +{ + // Test the calculateVisibleRect() function works correctly when the layer has a + // perspective projection onto the target surface. + + IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100)); + IntRect layerContentRect = IntRect(IntPoint(-50, -50), IntSize(200, 200)); + WebTransformationMatrix layerToSurfaceTransform; + + // Case 1: Even though the layer is twice as large as the surface, due to perspective + // foreshortening, the layer will fit fully in the surface when its translated + // more than the perspective amount. + layerToSurfaceTransform.makeIdentity(); + + // The following sequence of transforms applies the perspective about the center of the surface. + layerToSurfaceTransform.translate(50, 50); + layerToSurfaceTransform.applyPerspective(9); + layerToSurfaceTransform.translate(-50, -50); + + // This translate places the layer in front of the surface's projection plane. + layerToSurfaceTransform.translate3d(0, 0, -27); + + IntRect expected = IntRect(IntPoint(-50, -50), IntSize(200, 200)); + IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); + + // Case 2: same projection as before, except that the layer is also translated to the + // side, so that only the right half of the layer should be visible. + // + // Explanation of expected result: + // The perspective ratio is (z distance between layer and camera origin) / (z distance between projection plane and camera origin) == ((-27 - 9) / 9) + // Then, by similar triangles, if we want to move a layer by translating -50 units in projected surface units (so that only half of it is + // visible), then we would need to translate by (-36 / 9) * -50 == -200 in the layer's units. + // + layerToSurfaceTransform.translate3d(-200, 0, 0); + expected = IntRect(IntPoint(50, -50), IntSize(100, 200)); // The right half of the layer's bounding rect. + actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); +} + +TEST(CCLayerTreeHostCommonTest, verifyVisibleRectFor3dOrthographicIsNotClippedBehindSurface) +{ + // There is currently no explicit concept of an orthographic projection plane in our + // code (nor in the CSS spec to my knowledge). Therefore, layers that are technically + // behind the surface in an orthographic world should not be clipped when they are + // flattened to the surface. + + IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100)); + IntRect layerContentRect = IntRect(IntPoint(0, 0), IntSize(100, 100)); + WebTransformationMatrix layerToSurfaceTransform; + + // This sequence of transforms effectively rotates the layer about the y-axis at the + // center of the layer. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.translate(50, 0); + layerToSurfaceTransform.rotate3d(0, 45, 0); + layerToSurfaceTransform.translate(-50, 0); + + IntRect expected = IntRect(IntPoint(0, 0), IntSize(100, 100)); + IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); +} + +TEST(CCLayerTreeHostCommonTest, verifyVisibleRectFor3dPerspectiveWhenClippedByW) +{ + // Test the calculateVisibleRect() function works correctly when projecting a surface + // onto a layer, but the layer is partially behind the camera (not just behind the + // projection plane). In this case, the cartesian coordinates may seem to be valid, + // but actually they are not. The visibleRect needs to be properly clipped by the + // w = 0 plane in homogeneous coordinates before converting to cartesian coordinates. + + IntRect targetSurfaceRect = IntRect(IntPoint(-50, -50), IntSize(100, 100)); + IntRect layerContentRect = IntRect(IntPoint(-10, -1), IntSize(20, 2)); + WebTransformationMatrix layerToSurfaceTransform; + + // The layer is positioned so that the right half of the layer should be in front of + // the camera, while the other half is behind the surface's projection plane. The + // following sequence of transforms applies the perspective and rotation about the + // center of the layer. + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.applyPerspective(1); + layerToSurfaceTransform.translate3d(-2, 0, 1); + layerToSurfaceTransform.rotate3d(0, 45, 0); + + // Sanity check that this transform does indeed cause w < 0 when applying the + // transform, otherwise this code is not testing the intended scenario. + bool clipped = false; + CCMathUtil::mapQuad(layerToSurfaceTransform, FloatQuad(FloatRect(layerContentRect)), clipped); + ASSERT_TRUE(clipped); + + int expectedXPosition = 0; + int expectedWidth = 10; + IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_EQ(expectedXPosition, actual.x()); + EXPECT_EQ(expectedWidth, actual.width()); +} + +TEST(CCLayerTreeHostCommonTest, verifyVisibleRectForPerspectiveUnprojection) +{ + // To determine visibleRect in layer space, there needs to be an un-projection from + // surface space to layer space. When the original transform was a perspective + // projection that was clipped, it returns a rect that encloses the clipped bounds. + // Un-projecting this new rect may require clipping again. + + // This sequence of transforms causes one corner of the layer to protrude across the w = 0 plane, and should be clipped. + IntRect targetSurfaceRect = IntRect(IntPoint(-50, -50), IntSize(100, 100)); + IntRect layerContentRect = IntRect(IntPoint(-10, -10), IntSize(20, 20)); + WebTransformationMatrix layerToSurfaceTransform; + layerToSurfaceTransform.makeIdentity(); + layerToSurfaceTransform.applyPerspective(1); + layerToSurfaceTransform.translate3d(0, 0, -5); + layerToSurfaceTransform.rotate3d(0, 45, 0); + layerToSurfaceTransform.rotate3d(80, 0, 0); + + // Sanity check that un-projection does indeed cause w < 0, otherwise this code is not + // testing the intended scenario. + bool clipped = false; + FloatRect clippedRect = CCMathUtil::mapClippedRect(layerToSurfaceTransform, layerContentRect); + CCMathUtil::projectQuad(layerToSurfaceTransform.inverse(), FloatQuad(clippedRect), clipped); + ASSERT_TRUE(clipped); + + // Only the corner of the layer is not visible on the surface because of being + // clipped. But, the net result of rounding visible region to an axis-aligned rect is + // that the entire layer should still be considered visible. + IntRect expected = IntRect(IntPoint(-10, -10), IntSize(20, 20)); + IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform); + EXPECT_INT_RECT_EQ(expected, actual); +} + +TEST(CCLayerTreeHostCommonTest, verifyBackFaceCullingWithoutPreserves3d) +{ + // Verify the behavior of back-face culling when there are no preserve-3d layers. Note + // that 3d transforms still apply in this case, but they are "flattened" to each + // parent layer according to current W3C spec. + + const WebTransformationMatrix identityMatrix; + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> frontFacingChild = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> backFacingChild = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> frontFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> backFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> frontFacingChildOfFrontFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> backFacingChildOfFrontFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> frontFacingChildOfBackFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> backFacingChildOfBackFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + + parent->addChild(frontFacingChild); + parent->addChild(backFacingChild); + parent->addChild(frontFacingSurface); + parent->addChild(backFacingSurface); + frontFacingSurface->addChild(frontFacingChildOfFrontFacingSurface); + frontFacingSurface->addChild(backFacingChildOfFrontFacingSurface); + backFacingSurface->addChild(frontFacingChildOfBackFacingSurface); + backFacingSurface->addChild(backFacingChildOfBackFacingSurface); + + // Nothing is double-sided + frontFacingChild->setDoubleSided(false); + backFacingChild->setDoubleSided(false); + frontFacingSurface->setDoubleSided(false); + backFacingSurface->setDoubleSided(false); + frontFacingChildOfFrontFacingSurface->setDoubleSided(false); + backFacingChildOfFrontFacingSurface->setDoubleSided(false); + frontFacingChildOfBackFacingSurface->setDoubleSided(false); + backFacingChildOfBackFacingSurface->setDoubleSided(false); + + WebTransformationMatrix backfaceMatrix; + backfaceMatrix.translate(50, 50); + backfaceMatrix.rotate3d(0, 1, 0, 180); + backfaceMatrix.translate(-50, -50); + + // Having a descendant and opacity will force these to have render surfaces. + frontFacingSurface->setOpacity(0.5); + backFacingSurface->setOpacity(0.5); + + // Nothing preserves 3d. According to current W3C CSS Transforms spec, these layers + // should blindly use their own local transforms to determine back-face culling. + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(frontFacingChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(backFacingChild.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(frontFacingSurface.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(backFacingSurface.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(frontFacingChildOfFrontFacingSurface.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(backFacingChildOfFrontFacingSurface.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(frontFacingChildOfBackFacingSurface.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(backFacingChildOfBackFacingSurface.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + + // Verify which renderSurfaces were created. + EXPECT_FALSE(frontFacingChild->renderSurface()); + EXPECT_FALSE(backFacingChild->renderSurface()); + EXPECT_TRUE(frontFacingSurface->renderSurface()); + EXPECT_TRUE(backFacingSurface->renderSurface()); + EXPECT_FALSE(frontFacingChildOfFrontFacingSurface->renderSurface()); + EXPECT_FALSE(backFacingChildOfFrontFacingSurface->renderSurface()); + EXPECT_FALSE(frontFacingChildOfBackFacingSurface->renderSurface()); + EXPECT_FALSE(backFacingChildOfBackFacingSurface->renderSurface()); + + // Verify the renderSurfaceLayerList. + ASSERT_EQ(3u, renderSurfaceLayerList.size()); + EXPECT_EQ(parent->id(), renderSurfaceLayerList[0]->id()); + EXPECT_EQ(frontFacingSurface->id(), renderSurfaceLayerList[1]->id()); + // Even though the back facing surface LAYER gets culled, the other descendants should still be added, so the SURFACE should not be culled. + EXPECT_EQ(backFacingSurface->id(), renderSurfaceLayerList[2]->id()); + + // Verify root surface's layerList. + ASSERT_EQ(3u, renderSurfaceLayerList[0]->renderSurface()->layerList().size()); + EXPECT_EQ(frontFacingChild->id(), renderSurfaceLayerList[0]->renderSurface()->layerList()[0]->id()); + EXPECT_EQ(frontFacingSurface->id(), renderSurfaceLayerList[0]->renderSurface()->layerList()[1]->id()); + EXPECT_EQ(backFacingSurface->id(), renderSurfaceLayerList[0]->renderSurface()->layerList()[2]->id()); + + // Verify frontFacingSurface's layerList. + ASSERT_EQ(2u, renderSurfaceLayerList[1]->renderSurface()->layerList().size()); + EXPECT_EQ(frontFacingSurface->id(), renderSurfaceLayerList[1]->renderSurface()->layerList()[0]->id()); + EXPECT_EQ(frontFacingChildOfFrontFacingSurface->id(), renderSurfaceLayerList[1]->renderSurface()->layerList()[1]->id()); + + // Verify backFacingSurface's layerList; its own layer should be culled from the surface list. + ASSERT_EQ(1u, renderSurfaceLayerList[2]->renderSurface()->layerList().size()); + EXPECT_EQ(frontFacingChildOfBackFacingSurface->id(), renderSurfaceLayerList[2]->renderSurface()->layerList()[0]->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyBackFaceCullingWithPreserves3d) +{ + // Verify the behavior of back-face culling when preserves-3d transform style is used. + + const WebTransformationMatrix identityMatrix; + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> frontFacingChild = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> backFacingChild = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> frontFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> backFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> frontFacingChildOfFrontFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> backFacingChildOfFrontFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> frontFacingChildOfBackFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> backFacingChildOfBackFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> dummyReplicaLayer1 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> dummyReplicaLayer2 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + + parent->addChild(frontFacingChild); + parent->addChild(backFacingChild); + parent->addChild(frontFacingSurface); + parent->addChild(backFacingSurface); + frontFacingSurface->addChild(frontFacingChildOfFrontFacingSurface); + frontFacingSurface->addChild(backFacingChildOfFrontFacingSurface); + backFacingSurface->addChild(frontFacingChildOfBackFacingSurface); + backFacingSurface->addChild(backFacingChildOfBackFacingSurface); + + // Nothing is double-sided + frontFacingChild->setDoubleSided(false); + backFacingChild->setDoubleSided(false); + frontFacingSurface->setDoubleSided(false); + backFacingSurface->setDoubleSided(false); + frontFacingChildOfFrontFacingSurface->setDoubleSided(false); + backFacingChildOfFrontFacingSurface->setDoubleSided(false); + frontFacingChildOfBackFacingSurface->setDoubleSided(false); + backFacingChildOfBackFacingSurface->setDoubleSided(false); + + WebTransformationMatrix backfaceMatrix; + backfaceMatrix.translate(50, 50); + backfaceMatrix.rotate3d(0, 1, 0, 180); + backfaceMatrix.translate(-50, -50); + + // Opacity will not force creation of renderSurfaces in this case because of the + // preserve-3d transform style. Instead, an example of when a surface would be + // created with preserve-3d is when there is a replica layer. + frontFacingSurface->setReplicaLayer(dummyReplicaLayer1.get()); + backFacingSurface->setReplicaLayer(dummyReplicaLayer2.get()); + + // Each surface creates its own new 3d rendering context (as defined by W3C spec). + // According to current W3C CSS Transforms spec, layers in a 3d rendering context + // should use the transform with respect to that context. This 3d rendering context + // occurs when (a) parent's transform style is flat and (b) the layer's transform + // style is preserve-3d. + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); // parent transform style is flat. + setLayerPropertiesForTesting(frontFacingChild.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(backFacingChild.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(frontFacingSurface.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), true); // surface transform style is preserve-3d. + setLayerPropertiesForTesting(backFacingSurface.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), true); // surface transform style is preserve-3d. + setLayerPropertiesForTesting(frontFacingChildOfFrontFacingSurface.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(backFacingChildOfFrontFacingSurface.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(frontFacingChildOfBackFacingSurface.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(backFacingChildOfBackFacingSurface.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + + // Verify which renderSurfaces were created. + EXPECT_FALSE(frontFacingChild->renderSurface()); + EXPECT_FALSE(backFacingChild->renderSurface()); + EXPECT_TRUE(frontFacingSurface->renderSurface()); + EXPECT_FALSE(backFacingSurface->renderSurface()); + EXPECT_FALSE(frontFacingChildOfFrontFacingSurface->renderSurface()); + EXPECT_FALSE(backFacingChildOfFrontFacingSurface->renderSurface()); + EXPECT_FALSE(frontFacingChildOfBackFacingSurface->renderSurface()); + EXPECT_FALSE(backFacingChildOfBackFacingSurface->renderSurface()); + + // Verify the renderSurfaceLayerList. The back-facing surface should be culled. + ASSERT_EQ(2u, renderSurfaceLayerList.size()); + EXPECT_EQ(parent->id(), renderSurfaceLayerList[0]->id()); + EXPECT_EQ(frontFacingSurface->id(), renderSurfaceLayerList[1]->id()); + + // Verify root surface's layerList. + ASSERT_EQ(2u, renderSurfaceLayerList[0]->renderSurface()->layerList().size()); + EXPECT_EQ(frontFacingChild->id(), renderSurfaceLayerList[0]->renderSurface()->layerList()[0]->id()); + EXPECT_EQ(frontFacingSurface->id(), renderSurfaceLayerList[0]->renderSurface()->layerList()[1]->id()); + + // Verify frontFacingSurface's layerList. + ASSERT_EQ(2u, renderSurfaceLayerList[1]->renderSurface()->layerList().size()); + EXPECT_EQ(frontFacingSurface->id(), renderSurfaceLayerList[1]->renderSurface()->layerList()[0]->id()); + EXPECT_EQ(frontFacingChildOfFrontFacingSurface->id(), renderSurfaceLayerList[1]->renderSurface()->layerList()[1]->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyBackFaceCullingWithAnimatingTransforms) +{ + // Verify that layers are appropriately culled when their back face is showing and + // they are not double sided, while animations are going on. + // + // Layers that are animating do not get culled on the main thread, as their transforms should be + // treated as "unknown" so we can not be sure that their back face is really showing. + // + + const WebTransformationMatrix identityMatrix; + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> child = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> animatingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> childOfAnimatingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> animatingChild = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> child2 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + + parent->addChild(child); + parent->addChild(animatingSurface); + animatingSurface->addChild(childOfAnimatingSurface); + parent->addChild(animatingChild); + parent->addChild(child2); + + // Nothing is double-sided + child->setDoubleSided(false); + child2->setDoubleSided(false); + animatingSurface->setDoubleSided(false); + childOfAnimatingSurface->setDoubleSided(false); + animatingChild->setDoubleSided(false); + + WebTransformationMatrix backfaceMatrix; + backfaceMatrix.translate(50, 50); + backfaceMatrix.rotate3d(0, 1, 0, 180); + backfaceMatrix.translate(-50, -50); + + // Make our render surface. + animatingSurface->setForceRenderSurface(true); + + // Animate the transform on the render surface. + addAnimatedTransformToController(*animatingSurface->layerAnimationController(), 10, 30, 0); + // This is just an animating layer, not a surface. + addAnimatedTransformToController(*animatingChild->layerAnimationController(), 10, 30, 0); + + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(child.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(animatingSurface.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(childOfAnimatingSurface.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(animatingChild.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(child2.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + EXPECT_FALSE(child->renderSurface()); + EXPECT_TRUE(animatingSurface->renderSurface()); + EXPECT_FALSE(childOfAnimatingSurface->renderSurface()); + EXPECT_FALSE(animatingChild->renderSurface()); + EXPECT_FALSE(child2->renderSurface()); + + // Verify that the animatingChild and childOfAnimatingSurface were not culled, but that child was. + ASSERT_EQ(2u, renderSurfaceLayerList.size()); + EXPECT_EQ(parent->id(), renderSurfaceLayerList[0]->id()); + EXPECT_EQ(animatingSurface->id(), renderSurfaceLayerList[1]->id()); + + // The non-animating child be culled from the layer list for the parent render surface. + ASSERT_EQ(3u, renderSurfaceLayerList[0]->renderSurface()->layerList().size()); + EXPECT_EQ(animatingSurface->id(), renderSurfaceLayerList[0]->renderSurface()->layerList()[0]->id()); + EXPECT_EQ(animatingChild->id(), renderSurfaceLayerList[0]->renderSurface()->layerList()[1]->id()); + EXPECT_EQ(child2->id(), renderSurfaceLayerList[0]->renderSurface()->layerList()[2]->id()); + + ASSERT_EQ(2u, renderSurfaceLayerList[1]->renderSurface()->layerList().size()); + EXPECT_EQ(animatingSurface->id(), renderSurfaceLayerList[1]->renderSurface()->layerList()[0]->id()); + EXPECT_EQ(childOfAnimatingSurface->id(), renderSurfaceLayerList[1]->renderSurface()->layerList()[1]->id()); + + EXPECT_FALSE(child2->visibleContentRect().isEmpty()); + + // The animating layers should have a visibleContentRect that represents the area of the front face that is within the viewport. + EXPECT_EQ(animatingChild->visibleContentRect(), IntRect(IntPoint(), animatingChild->contentBounds())); + EXPECT_EQ(animatingSurface->visibleContentRect(), IntRect(IntPoint(), animatingSurface->contentBounds())); + // And layers in the subtree of the animating layer should have valid visibleContentRects also. + EXPECT_EQ(childOfAnimatingSurface->visibleContentRect(), IntRect(IntPoint(), childOfAnimatingSurface->contentBounds())); +} + +TEST(CCLayerTreeHostCommonTest, verifyBackFaceCullingWithPreserves3dForFlatteningSurface) +{ + // Verify the behavior of back-face culling for a renderSurface that is created + // when it flattens its subtree, and its parent has preserves-3d. + + const WebTransformationMatrix identityMatrix; + RefPtr<LayerChromium> parent = LayerChromium::create(); + RefPtr<LayerChromiumWithForcedDrawsContent> frontFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> backFacingSurface = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> child1 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + RefPtr<LayerChromiumWithForcedDrawsContent> child2 = adoptRef(new LayerChromiumWithForcedDrawsContent()); + + parent->addChild(frontFacingSurface); + parent->addChild(backFacingSurface); + frontFacingSurface->addChild(child1); + backFacingSurface->addChild(child2); + + // RenderSurfaces are not double-sided + frontFacingSurface->setDoubleSided(false); + backFacingSurface->setDoubleSided(false); + + WebTransformationMatrix backfaceMatrix; + backfaceMatrix.translate(50, 50); + backfaceMatrix.rotate3d(0, 1, 0, 180); + backfaceMatrix.translate(-50, -50); + + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), true); // parent transform style is preserve3d. + setLayerPropertiesForTesting(frontFacingSurface.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); // surface transform style is flat. + setLayerPropertiesForTesting(backFacingSurface.get(), backfaceMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); // surface transform style is flat. + setLayerPropertiesForTesting(child1.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + setLayerPropertiesForTesting(child2.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), 1, dummyMaxTextureSize, renderSurfaceLayerList); + + // Verify which renderSurfaces were created. + EXPECT_TRUE(frontFacingSurface->renderSurface()); + EXPECT_FALSE(backFacingSurface->renderSurface()); // because it should be culled + EXPECT_FALSE(child1->renderSurface()); + EXPECT_FALSE(child2->renderSurface()); + + // Verify the renderSurfaceLayerList. The back-facing surface should be culled. + ASSERT_EQ(2u, renderSurfaceLayerList.size()); + EXPECT_EQ(parent->id(), renderSurfaceLayerList[0]->id()); + EXPECT_EQ(frontFacingSurface->id(), renderSurfaceLayerList[1]->id()); + + // Verify root surface's layerList. + ASSERT_EQ(1u, renderSurfaceLayerList[0]->renderSurface()->layerList().size()); + EXPECT_EQ(frontFacingSurface->id(), renderSurfaceLayerList[0]->renderSurface()->layerList()[0]->id()); + + // Verify frontFacingSurface's layerList. + ASSERT_EQ(2u, renderSurfaceLayerList[1]->renderSurface()->layerList().size()); + EXPECT_EQ(frontFacingSurface->id(), renderSurfaceLayerList[1]->renderSurface()->layerList()[0]->id()); + EXPECT_EQ(child1->id(), renderSurfaceLayerList[1]->renderSurface()->layerList()[1]->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForEmptyLayerList) +{ + // Hit testing on an empty renderSurfaceLayerList should return a null pointer. + DebugScopedSetImplThread thisScopeIsOnImplThread; + + Vector<CCLayerImpl*> renderSurfaceLayerList; + + IntPoint testPoint(0, 0); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(10, 20); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForSingleLayer) +{ + DebugScopedSetImplThread thisScopeIsOnImplThread; + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(12345); + + WebTransformationMatrix identityMatrix; + FloatPoint anchor(0, 0); + FloatPoint position(0, 0); + IntSize bounds(100, 100); + setLayerPropertiesForTesting(root.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + root->setDrawsContent(true); + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + ASSERT_EQ(1u, renderSurfaceLayerList.size()); + ASSERT_EQ(1u, root->renderSurface()->layerList().size()); + + // Hit testing for a point outside the layer should return a null pointer. + IntPoint testPoint(101, 101); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(-1, -1); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Hit testing for a point inside should return the root layer. + testPoint = IntPoint(1, 1); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(12345, resultLayer->id()); + + testPoint = IntPoint(99, 99); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(12345, resultLayer->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForUninvertibleTransform) +{ + DebugScopedSetImplThread thisScopeIsOnImplThread; + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(12345); + + WebTransformationMatrix uninvertibleTransform; + uninvertibleTransform.setM11(0); + uninvertibleTransform.setM22(0); + uninvertibleTransform.setM33(0); + uninvertibleTransform.setM44(0); + ASSERT_FALSE(uninvertibleTransform.isInvertible()); + + WebTransformationMatrix identityMatrix; + FloatPoint anchor(0, 0); + FloatPoint position(0, 0); + IntSize bounds(100, 100); + setLayerPropertiesForTesting(root.get(), uninvertibleTransform, identityMatrix, anchor, position, bounds, false); + root->setDrawsContent(true); + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + ASSERT_EQ(1u, renderSurfaceLayerList.size()); + ASSERT_EQ(1u, root->renderSurface()->layerList().size()); + ASSERT_FALSE(root->screenSpaceTransform().isInvertible()); + + // Hit testing any point should not hit the layer. If the invertible matrix is + // accidentally ignored and treated like an identity, then the hit testing will + // incorrectly hit the layer when it shouldn't. + IntPoint testPoint(1, 1); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(10, 10); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(10, 30); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(50, 50); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(67, 48); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(99, 99); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(-1, -1); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForSinglePositionedLayer) +{ + DebugScopedSetImplThread thisScopeIsOnImplThread; + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(12345); + + WebTransformationMatrix identityMatrix; + FloatPoint anchor(0, 0); + FloatPoint position(50, 50); // this layer is positioned, and hit testing should correctly know where the layer is located. + IntSize bounds(100, 100); + setLayerPropertiesForTesting(root.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + root->setDrawsContent(true); + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + ASSERT_EQ(1u, renderSurfaceLayerList.size()); + ASSERT_EQ(1u, root->renderSurface()->layerList().size()); + + // Hit testing for a point outside the layer should return a null pointer. + IntPoint testPoint(49, 49); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Even though the layer exists at (101, 101), it should not be visible there since the root renderSurface would clamp it. + testPoint = IntPoint(101, 101); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Hit testing for a point inside should return the root layer. + testPoint = IntPoint(51, 51); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(12345, resultLayer->id()); + + testPoint = IntPoint(99, 99); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(12345, resultLayer->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForSingleRotatedLayer) +{ + DebugScopedSetImplThread thisScopeIsOnImplThread; + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(12345); + + WebTransformationMatrix identityMatrix; + WebTransformationMatrix rotation45DegreesAboutCenter; + rotation45DegreesAboutCenter.translate(50, 50); + rotation45DegreesAboutCenter.rotate3d(0, 0, 45); + rotation45DegreesAboutCenter.translate(-50, -50); + FloatPoint anchor(0, 0); + FloatPoint position(0, 0); + IntSize bounds(100, 100); + setLayerPropertiesForTesting(root.get(), rotation45DegreesAboutCenter, identityMatrix, anchor, position, bounds, false); + root->setDrawsContent(true); + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + ASSERT_EQ(1u, renderSurfaceLayerList.size()); + ASSERT_EQ(1u, root->renderSurface()->layerList().size()); + + // Hit testing for points outside the layer. + // These corners would have been inside the un-transformed layer, but they should not hit the correctly transformed layer. + IntPoint testPoint(99, 99); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(1, 1); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Hit testing for a point inside should return the root layer. + testPoint = IntPoint(1, 50); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(12345, resultLayer->id()); + + // Hit testing the corners that would overlap the unclipped layer, but are outside the clipped region. + testPoint = IntPoint(50, -1); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_FALSE(resultLayer); + + testPoint = IntPoint(-1, 50); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_FALSE(resultLayer); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForSinglePerspectiveLayer) +{ + DebugScopedSetImplThread thisScopeIsOnImplThread; + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(12345); + + WebTransformationMatrix identityMatrix; + + // perspectiveProjectionAboutCenter * translationByZ is designed so that the 100 x 100 layer becomes 50 x 50, and remains centered at (50, 50). + WebTransformationMatrix perspectiveProjectionAboutCenter; + perspectiveProjectionAboutCenter.translate(50, 50); + perspectiveProjectionAboutCenter.applyPerspective(1); + perspectiveProjectionAboutCenter.translate(-50, -50); + WebTransformationMatrix translationByZ; + translationByZ.translate3d(0, 0, -1); + + FloatPoint anchor(0, 0); + FloatPoint position(0, 0); + IntSize bounds(100, 100); + setLayerPropertiesForTesting(root.get(), perspectiveProjectionAboutCenter * translationByZ, identityMatrix, anchor, position, bounds, false); + root->setDrawsContent(true); + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + ASSERT_EQ(1u, renderSurfaceLayerList.size()); + ASSERT_EQ(1u, root->renderSurface()->layerList().size()); + + // Hit testing for points outside the layer. + // These corners would have been inside the un-transformed layer, but they should not hit the correctly transformed layer. + IntPoint testPoint(24, 24); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(76, 76); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Hit testing for a point inside should return the root layer. + testPoint = IntPoint(26, 26); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(12345, resultLayer->id()); + + testPoint = IntPoint(74, 74); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(12345, resultLayer->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForSingleLayerWithScaledContents) +{ + // A layer's visibleContentRect is actually in the layer's content space. The + // screenSpaceTransform converts from the layer's origin space to screen space. This + // test makes sure that hit testing works correctly accounts for the contents scale. + // A contentsScale that is not 1 effectively forces a non-identity transform between + // layer's content space and layer's origin space. The hit testing code must take this into account. + // + // To test this, the layer is positioned at (25, 25), and is size (50, 50). If + // contentsScale is ignored, then hit testing will mis-interpret the visibleContentRect + // as being larger than the actual bounds of the layer. + // + DebugScopedSetImplThread thisScopeIsOnImplThread; + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + + WebTransformationMatrix identityMatrix; + FloatPoint anchor(0, 0); + + setLayerPropertiesForTesting(root.get(), identityMatrix, identityMatrix, anchor, FloatPoint(0, 0), IntSize(100, 100), false); + + { + FloatPoint position(25, 25); + IntSize bounds(50, 50); + OwnPtr<CCLayerImpl> testLayer = CCLayerImpl::create(12345); + setLayerPropertiesForTesting(testLayer.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + + // override contentBounds + testLayer->setContentBounds(IntSize(100, 100)); + + testLayer->setDrawsContent(true); + root->addChild(testLayer.release()); + } + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + // The visibleContentRect for testLayer is actually 100x100, even though its layout size is 50x50, positioned at 25x25. + CCLayerImpl* testLayer = root->children()[0].get(); + EXPECT_INT_RECT_EQ(IntRect(IntPoint::zero(), IntSize(100, 100)), testLayer->visibleContentRect()); + ASSERT_EQ(1u, renderSurfaceLayerList.size()); + ASSERT_EQ(1u, root->renderSurface()->layerList().size()); + + // Hit testing for a point outside the layer should return a null pointer (the root layer does not draw content, so it will not be hit tested either). + IntPoint testPoint(101, 101); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(24, 24); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(76, 76); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Hit testing for a point inside should return the test layer. + testPoint = IntPoint(26, 26); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(12345, resultLayer->id()); + + testPoint = IntPoint(74, 74); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(12345, resultLayer->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForSimpleClippedLayer) +{ + // Test that hit-testing will only work for the visible portion of a layer, and not + // the entire layer bounds. Here we just test the simple axis-aligned case. + DebugScopedSetImplThread thisScopeIsOnImplThread; + + WebTransformationMatrix identityMatrix; + FloatPoint anchor(0, 0); + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + setLayerPropertiesForTesting(root.get(), identityMatrix, identityMatrix, anchor, FloatPoint(0, 0), IntSize(100, 100), false); + + { + OwnPtr<CCLayerImpl> clippingLayer = CCLayerImpl::create(123); + FloatPoint position(25, 25); // this layer is positioned, and hit testing should correctly know where the layer is located. + IntSize bounds(50, 50); + setLayerPropertiesForTesting(clippingLayer.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + clippingLayer->setMasksToBounds(true); + + OwnPtr<CCLayerImpl> child = CCLayerImpl::create(456); + position = FloatPoint(-50, -50); + bounds = IntSize(300, 300); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + child->setDrawsContent(true); + clippingLayer->addChild(child.release()); + root->addChild(clippingLayer.release()); + } + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + ASSERT_EQ(1u, renderSurfaceLayerList.size()); + ASSERT_EQ(1u, root->renderSurface()->layerList().size()); + ASSERT_EQ(456, root->renderSurface()->layerList()[0]->id()); + + // Hit testing for a point outside the layer should return a null pointer. + // Despite the child layer being very large, it should be clipped to the root layer's bounds. + IntPoint testPoint(24, 24); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Even though the layer exists at (101, 101), it should not be visible there since the clippingLayer would clamp it. + testPoint = IntPoint(76, 76); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Hit testing for a point inside should return the child layer. + testPoint = IntPoint(26, 26); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(456, resultLayer->id()); + + testPoint = IntPoint(74, 74); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(456, resultLayer->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForMultiClippedRotatedLayer) +{ + // This test checks whether hit testing correctly avoids hit testing with multiple + // ancestors that clip in non axis-aligned ways. To pass this test, the hit testing + // algorithm needs to recognize that multiple parent layers may clip the layer, and + // should not actually hit those clipped areas. + // + // The child and grandChild layers are both initialized to clip the rotatedLeaf. The + // child layer is rotated about the top-left corner, so that the root + child clips + // combined create a triangle. The rotatedLeaf will only be visible where it overlaps + // this triangle. + // + DebugScopedSetImplThread thisScopeIsOnImplThread; + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(123); + + WebTransformationMatrix identityMatrix; + FloatPoint anchor(0, 0); + FloatPoint position(0, 0); + IntSize bounds(100, 100); + setLayerPropertiesForTesting(root.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + root->setMasksToBounds(true); + + { + OwnPtr<CCLayerImpl> child = CCLayerImpl::create(456); + OwnPtr<CCLayerImpl> grandChild = CCLayerImpl::create(789); + OwnPtr<CCLayerImpl> rotatedLeaf = CCLayerImpl::create(2468); + + position = FloatPoint(10, 10); + bounds = IntSize(80, 80); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + child->setMasksToBounds(true); + + WebTransformationMatrix rotation45DegreesAboutCorner; + rotation45DegreesAboutCorner.rotate3d(0, 0, 45); + + position = FloatPoint(0, 0); // remember, positioned with respect to its parent which is already at 10, 10 + bounds = IntSize(200, 200); // to ensure it covers at least sqrt(2) * 100. + setLayerPropertiesForTesting(grandChild.get(), rotation45DegreesAboutCorner, identityMatrix, anchor, position, bounds, false); + grandChild->setMasksToBounds(true); + + // Rotates about the center of the layer + WebTransformationMatrix rotatedLeafTransform; + rotatedLeafTransform.translate(-10, -10); // cancel out the grandParent's position + rotatedLeafTransform.rotate3d(0, 0, -45); // cancel out the corner 45-degree rotation of the parent. + rotatedLeafTransform.translate(50, 50); + rotatedLeafTransform.rotate3d(0, 0, 45); + rotatedLeafTransform.translate(-50, -50); + position = FloatPoint(0, 0); + bounds = IntSize(100, 100); + setLayerPropertiesForTesting(rotatedLeaf.get(), rotatedLeafTransform, identityMatrix, anchor, position, bounds, false); + rotatedLeaf->setDrawsContent(true); + + grandChild->addChild(rotatedLeaf.release()); + child->addChild(grandChild.release()); + root->addChild(child.release()); + } + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + // The grandChild is expected to create a renderSurface because it masksToBounds and is not axis aligned. + ASSERT_EQ(2u, renderSurfaceLayerList.size()); + ASSERT_EQ(1u, renderSurfaceLayerList[0]->renderSurface()->layerList().size()); + ASSERT_EQ(789, renderSurfaceLayerList[0]->renderSurface()->layerList()[0]->id()); // grandChild's surface. + ASSERT_EQ(1u, renderSurfaceLayerList[1]->renderSurface()->layerList().size()); + ASSERT_EQ(2468, renderSurfaceLayerList[1]->renderSurface()->layerList()[0]->id()); + + // (11, 89) is close to the the bottom left corner within the clip, but it is not inside the layer. + IntPoint testPoint(11, 89); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Closer inwards from the bottom left will overlap the layer. + testPoint = IntPoint(25, 75); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(2468, resultLayer->id()); + + // (4, 50) is inside the unclipped layer, but that corner of the layer should be + // clipped away by the grandParent and should not get hit. If hit testing blindly uses + // visibleContentRect without considering how parent may clip the layer, then hit + // testing would accidentally think that the point successfully hits the layer. + testPoint = IntPoint(4, 50); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // (11, 50) is inside the layer and within the clipped area. + testPoint = IntPoint(11, 50); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(2468, resultLayer->id()); + + // Around the middle, just to the right and up, would have hit the layer except that + // that area should be clipped away by the parent. + testPoint = IntPoint(51, 51); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Around the middle, just to the left and down, should successfully hit the layer. + testPoint = IntPoint(49, 51); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(2468, resultLayer->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForNonClippingIntermediateLayer) +{ + // This test checks that hit testing code does not accidentally clip to layer + // bounds for a layer that actually does not clip. + DebugScopedSetImplThread thisScopeIsOnImplThread; + + WebTransformationMatrix identityMatrix; + FloatPoint anchor(0, 0); + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + setLayerPropertiesForTesting(root.get(), identityMatrix, identityMatrix, anchor, FloatPoint(0, 0), IntSize(100, 100), false); + + { + OwnPtr<CCLayerImpl> intermediateLayer = CCLayerImpl::create(123); + FloatPoint position(10, 10); // this layer is positioned, and hit testing should correctly know where the layer is located. + IntSize bounds(50, 50); + setLayerPropertiesForTesting(intermediateLayer.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + // Sanity check the intermediate layer should not clip. + ASSERT_FALSE(intermediateLayer->masksToBounds()); + ASSERT_FALSE(intermediateLayer->maskLayer()); + + // The child of the intermediateLayer is translated so that it does not overlap intermediateLayer at all. + // If child is incorrectly clipped, we would not be able to hit it successfully. + OwnPtr<CCLayerImpl> child = CCLayerImpl::create(456); + position = FloatPoint(60, 60); // 70, 70 in screen space + bounds = IntSize(20, 20); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + child->setDrawsContent(true); + intermediateLayer->addChild(child.release()); + root->addChild(intermediateLayer.release()); + } + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + ASSERT_EQ(1u, renderSurfaceLayerList.size()); + ASSERT_EQ(1u, root->renderSurface()->layerList().size()); + ASSERT_EQ(456, root->renderSurface()->layerList()[0]->id()); + + // Hit testing for a point outside the layer should return a null pointer. + IntPoint testPoint(69, 69); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + testPoint = IntPoint(91, 91); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + EXPECT_FALSE(resultLayer); + + // Hit testing for a point inside should return the child layer. + testPoint = IntPoint(71, 71); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(456, resultLayer->id()); + + testPoint = IntPoint(89, 89); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(456, resultLayer->id()); +} + + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForMultipleLayers) +{ + DebugScopedSetImplThread thisScopeIsOnImplThread; + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + + WebTransformationMatrix identityMatrix; + FloatPoint anchor(0, 0); + FloatPoint position(0, 0); + IntSize bounds(100, 100); + setLayerPropertiesForTesting(root.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + root->setDrawsContent(true); + + { + // child 1 and child2 are initialized to overlap between x=50 and x=60. + // grandChild is set to overlap both child1 and child2 between y=50 and y=60. + // The expected stacking order is: + // (front) child2, (second) grandChild, (third) child1, and (back) the root layer behind all other layers. + + OwnPtr<CCLayerImpl> child1 = CCLayerImpl::create(2); + OwnPtr<CCLayerImpl> child2 = CCLayerImpl::create(3); + OwnPtr<CCLayerImpl> grandChild1 = CCLayerImpl::create(4); + + position = FloatPoint(10, 10); + bounds = IntSize(50, 50); + setLayerPropertiesForTesting(child1.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + child1->setDrawsContent(true); + + position = FloatPoint(50, 10); + bounds = IntSize(50, 50); + setLayerPropertiesForTesting(child2.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + child2->setDrawsContent(true); + + // Remember that grandChild is positioned with respect to its parent (i.e. child1). + // In screen space, the intended position is (10, 50), with size 100 x 50. + position = FloatPoint(0, 40); + bounds = IntSize(100, 50); + setLayerPropertiesForTesting(grandChild1.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + grandChild1->setDrawsContent(true); + + child1->addChild(grandChild1.release()); + root->addChild(child1.release()); + root->addChild(child2.release()); + } + + CCLayerImpl* child1 = root->children()[0].get(); + CCLayerImpl* child2 = root->children()[1].get(); + CCLayerImpl* grandChild1 = child1->children()[0].get(); + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + ASSERT_TRUE(child1); + ASSERT_TRUE(child2); + ASSERT_TRUE(grandChild1); + ASSERT_EQ(1u, renderSurfaceLayerList.size()); + ASSERT_EQ(4u, root->renderSurface()->layerList().size()); + ASSERT_EQ(1, root->renderSurface()->layerList()[0]->id()); // root layer + ASSERT_EQ(2, root->renderSurface()->layerList()[1]->id()); // child1 + ASSERT_EQ(4, root->renderSurface()->layerList()[2]->id()); // grandChild1 + ASSERT_EQ(3, root->renderSurface()->layerList()[3]->id()); // child2 + + // Nothing overlaps the rootLayer at (1, 1), so hit testing there should find the root layer. + IntPoint testPoint = IntPoint(1, 1); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(1, resultLayer->id()); + + // At (15, 15), child1 and root are the only layers. child1 is expected to be on top. + testPoint = IntPoint(15, 15); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(2, resultLayer->id()); + + // At (51, 20), child1 and child2 overlap. child2 is expected to be on top. + testPoint = IntPoint(51, 20); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(3, resultLayer->id()); + + // At (80, 51), child2 and grandChild1 overlap. child2 is expected to be on top. + testPoint = IntPoint(80, 51); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(3, resultLayer->id()); + + // At (51, 51), all layers overlap each other. child2 is expected to be on top of all other layers. + testPoint = IntPoint(51, 51); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(3, resultLayer->id()); + + // At (20, 51), child1 and grandChild1 overlap. grandChild1 is expected to be on top. + testPoint = IntPoint(20, 51); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(4, resultLayer->id()); +} + +TEST(CCLayerTreeHostCommonTest, verifyHitTestingForMultipleLayerLists) +{ + // + // The geometry is set up similarly to the previous case, but + // all layers are forced to be renderSurfaces now. + // + DebugScopedSetImplThread thisScopeIsOnImplThread; + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + + WebTransformationMatrix identityMatrix; + FloatPoint anchor(0, 0); + FloatPoint position(0, 0); + IntSize bounds(100, 100); + setLayerPropertiesForTesting(root.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + root->setDrawsContent(true); + + { + // child 1 and child2 are initialized to overlap between x=50 and x=60. + // grandChild is set to overlap both child1 and child2 between y=50 and y=60. + // The expected stacking order is: + // (front) child2, (second) grandChild, (third) child1, and (back) the root layer behind all other layers. + + OwnPtr<CCLayerImpl> child1 = CCLayerImpl::create(2); + OwnPtr<CCLayerImpl> child2 = CCLayerImpl::create(3); + OwnPtr<CCLayerImpl> grandChild1 = CCLayerImpl::create(4); + + position = FloatPoint(10, 10); + bounds = IntSize(50, 50); + setLayerPropertiesForTesting(child1.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + child1->setDrawsContent(true); + child1->setForceRenderSurface(true); + + position = FloatPoint(50, 10); + bounds = IntSize(50, 50); + setLayerPropertiesForTesting(child2.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + child2->setDrawsContent(true); + child2->setForceRenderSurface(true); + + // Remember that grandChild is positioned with respect to its parent (i.e. child1). + // In screen space, the intended position is (10, 50), with size 100 x 50. + position = FloatPoint(0, 40); + bounds = IntSize(100, 50); + setLayerPropertiesForTesting(grandChild1.get(), identityMatrix, identityMatrix, anchor, position, bounds, false); + grandChild1->setDrawsContent(true); + grandChild1->setForceRenderSurface(true); + + child1->addChild(grandChild1.release()); + root->addChild(child1.release()); + root->addChild(child2.release()); + } + + CCLayerImpl* child1 = root->children()[0].get(); + CCLayerImpl* child2 = root->children()[1].get(); + CCLayerImpl* grandChild1 = child1->children()[0].get(); + + Vector<CCLayerImpl*> renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + CCLayerTreeHostCommon::calculateDrawTransforms(root.get(), root->bounds(), 1, 0, dummyMaxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + // Sanity check the scenario we just created. + ASSERT_TRUE(child1); + ASSERT_TRUE(child2); + ASSERT_TRUE(grandChild1); + ASSERT_TRUE(child1->renderSurface()); + ASSERT_TRUE(child2->renderSurface()); + ASSERT_TRUE(grandChild1->renderSurface()); + ASSERT_EQ(4u, renderSurfaceLayerList.size()); + ASSERT_EQ(3u, root->renderSurface()->layerList().size()); // The root surface has the root layer, and child1's and child2's renderSurfaces. + ASSERT_EQ(2u, child1->renderSurface()->layerList().size()); // The child1 surface has the child1 layer and grandChild1's renderSurface. + ASSERT_EQ(1u, child2->renderSurface()->layerList().size()); + ASSERT_EQ(1u, grandChild1->renderSurface()->layerList().size()); + ASSERT_EQ(1, renderSurfaceLayerList[0]->id()); // root layer + ASSERT_EQ(2, renderSurfaceLayerList[1]->id()); // child1 + ASSERT_EQ(4, renderSurfaceLayerList[2]->id()); // grandChild1 + ASSERT_EQ(3, renderSurfaceLayerList[3]->id()); // child2 + + // Nothing overlaps the rootLayer at (1, 1), so hit testing there should find the root layer. + IntPoint testPoint = IntPoint(1, 1); + CCLayerImpl* resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(1, resultLayer->id()); + + // At (15, 15), child1 and root are the only layers. child1 is expected to be on top. + testPoint = IntPoint(15, 15); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(2, resultLayer->id()); + + // At (51, 20), child1 and child2 overlap. child2 is expected to be on top. + testPoint = IntPoint(51, 20); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(3, resultLayer->id()); + + // At (80, 51), child2 and grandChild1 overlap. child2 is expected to be on top. + testPoint = IntPoint(80, 51); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(3, resultLayer->id()); + + // At (51, 51), all layers overlap each other. child2 is expected to be on top of all other layers. + testPoint = IntPoint(51, 51); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(3, resultLayer->id()); + + // At (20, 51), child1 and grandChild1 overlap. grandChild1 is expected to be on top. + testPoint = IntPoint(20, 51); + resultLayer = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(testPoint, renderSurfaceLayerList); + ASSERT_TRUE(resultLayer); + EXPECT_EQ(4, resultLayer->id()); +} + +class MockContentLayerDelegate : public ContentLayerDelegate { +public: + MockContentLayerDelegate() { } + virtual ~MockContentLayerDelegate() { } + virtual void paintContents(SkCanvas*, const IntRect& clip, FloatRect& opaque) OVERRIDE { } +}; + +PassRefPtr<ContentLayerChromium> createDrawableContentLayerChromium(ContentLayerDelegate* delegate) +{ + RefPtr<ContentLayerChromium> toReturn = ContentLayerChromium::create(delegate); + toReturn->setIsDrawable(true); + return toReturn.release(); +} + +TEST(CCLayerTreeHostCommonTest, verifyLayerTransformsInHighDPI) +{ + // Verify draw and screen space transforms of layers not in a surface. + MockContentLayerDelegate delegate; + WebTransformationMatrix identityMatrix; + + RefPtr<ContentLayerChromium> parent = createDrawableContentLayerChromium(&delegate); + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), true); + + RefPtr<ContentLayerChromium> child = createDrawableContentLayerChromium(&delegate); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(2, 2), IntSize(10, 10), true); + + RefPtr<ContentLayerChromium> childNoScale = createDrawableContentLayerChromium(&delegate); + setLayerPropertiesForTesting(childNoScale.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(2, 2), IntSize(10, 10), true); + + parent->addChild(child); + parent->addChild(childNoScale); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + + const double deviceScaleFactor = 2.5; + parent->setContentsScale(deviceScaleFactor); + child->setContentsScale(deviceScaleFactor); + EXPECT_EQ(childNoScale->contentsScale(), 1); + + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), deviceScaleFactor, dummyMaxTextureSize, renderSurfaceLayerList); + + EXPECT_EQ(1u, renderSurfaceLayerList.size()); + + // Verify parent transforms + WebTransformationMatrix expectedParentTransform; + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedParentTransform, parent->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedParentTransform, parent->drawTransform()); + + // Verify results of transformed parent rects + FloatRect parentContentBounds(FloatPoint(), FloatSize(parent->contentBounds())); + + FloatRect parentDrawRect = CCMathUtil::mapClippedRect(parent->drawTransform(), parentContentBounds); + FloatRect parentScreenSpaceRect = CCMathUtil::mapClippedRect(parent->screenSpaceTransform(), parentContentBounds); + + FloatRect expectedParentDrawRect(FloatPoint(), parent->bounds()); + expectedParentDrawRect.scale(deviceScaleFactor); + EXPECT_FLOAT_RECT_EQ(expectedParentDrawRect, parentDrawRect); + EXPECT_FLOAT_RECT_EQ(expectedParentDrawRect, parentScreenSpaceRect); + + // Verify child transforms + WebTransformationMatrix expectedChildTransform; + expectedChildTransform.translate(deviceScaleFactor * child->position().x(), deviceScaleFactor * child->position().y()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildTransform, child->screenSpaceTransform()); + + // Verify results of transformed child rects + FloatRect childContentBounds(FloatPoint(), FloatSize(child->contentBounds())); + + FloatRect childDrawRect = CCMathUtil::mapClippedRect(child->drawTransform(), childContentBounds); + FloatRect childScreenSpaceRect = CCMathUtil::mapClippedRect(child->screenSpaceTransform(), childContentBounds); + + FloatRect expectedChildDrawRect(FloatPoint(), child->bounds()); + expectedChildDrawRect.move(child->position().x(), child->position().y()); + expectedChildDrawRect.scale(deviceScaleFactor); + EXPECT_FLOAT_RECT_EQ(expectedChildDrawRect, childDrawRect); + EXPECT_FLOAT_RECT_EQ(expectedChildDrawRect, childScreenSpaceRect); + + // Verify childNoScale transforms + WebTransformationMatrix expectedChildNoScaleTransform = child->drawTransform(); + // All transforms operate on content rects. The child's content rect + // incorporates device scale, but the childNoScale does not; add it here. + expectedChildNoScaleTransform.scale(deviceScaleFactor); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildNoScaleTransform, childNoScale->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedChildNoScaleTransform, childNoScale->screenSpaceTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifyRenderSurfaceTransformsInHighDPI) +{ + MockContentLayerDelegate delegate; + WebTransformationMatrix identityMatrix; + + RefPtr<ContentLayerChromium> parent = createDrawableContentLayerChromium(&delegate); + setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(30, 30), true); + + RefPtr<ContentLayerChromium> child = createDrawableContentLayerChromium(&delegate); + setLayerPropertiesForTesting(child.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(2, 2), IntSize(10, 10), true); + + WebTransformationMatrix replicaTransform; + replicaTransform.scaleNonUniform(1, -1); + RefPtr<ContentLayerChromium> replica = createDrawableContentLayerChromium(&delegate); + setLayerPropertiesForTesting(replica.get(), replicaTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(2, 2), IntSize(10, 10), true); + + // This layer should end up in the same surface as child, with the same draw + // and screen space transforms. + RefPtr<ContentLayerChromium> duplicateChildNonOwner = createDrawableContentLayerChromium(&delegate); + setLayerPropertiesForTesting(duplicateChildNonOwner.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 10), true); + + parent->addChild(child); + child->addChild(duplicateChildNonOwner); + child->setReplicaLayer(replica.get()); + + Vector<RefPtr<LayerChromium> > renderSurfaceLayerList; + int dummyMaxTextureSize = 512; + + const double deviceScaleFactor = 1.5; + parent->setContentsScale(deviceScaleFactor); + child->setContentsScale(deviceScaleFactor); + duplicateChildNonOwner->setContentsScale(deviceScaleFactor); + replica->setContentsScale(deviceScaleFactor); + + CCLayerTreeHostCommon::calculateDrawTransforms(parent.get(), parent->bounds(), deviceScaleFactor, dummyMaxTextureSize, renderSurfaceLayerList); + + // We should have two render surfaces. The root's render surface and child's + // render surface (it needs one because it has a replica layer). + EXPECT_EQ(2u, renderSurfaceLayerList.size()); + + WebTransformationMatrix expectedParentTransform; + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedParentTransform, parent->screenSpaceTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedParentTransform, parent->drawTransform()); + + WebTransformationMatrix expectedDrawTransform; + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedDrawTransform, child->drawTransform()); + + WebTransformationMatrix expectedScreenSpaceTransform; + expectedScreenSpaceTransform.translate(deviceScaleFactor * child->position().x(), deviceScaleFactor * child->position().y()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedScreenSpaceTransform, child->screenSpaceTransform()); + + WebTransformationMatrix expectedDuplicateChildDrawTransform = child->drawTransform(); + EXPECT_TRANSFORMATION_MATRIX_EQ(child->drawTransform(), duplicateChildNonOwner->drawTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ(child->screenSpaceTransform(), duplicateChildNonOwner->screenSpaceTransform()); + EXPECT_INT_RECT_EQ(child->drawableContentRect(), duplicateChildNonOwner->drawableContentRect()); + EXPECT_EQ(child->contentBounds(), duplicateChildNonOwner->contentBounds()); + + WebTransformationMatrix expectedRenderSurfaceDrawTransform; + expectedRenderSurfaceDrawTransform.translate(deviceScaleFactor * child->position().x(), deviceScaleFactor * child->position().y()); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedRenderSurfaceDrawTransform, child->renderSurface()->drawTransform()); + + WebTransformationMatrix expectedSurfaceDrawTransform; + expectedSurfaceDrawTransform.translate(deviceScaleFactor * 2, deviceScaleFactor * 2); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedSurfaceDrawTransform, child->renderSurface()->drawTransform()); + + WebTransformationMatrix expectedSurfaceScreenSpaceTransform; + expectedSurfaceScreenSpaceTransform.translate(deviceScaleFactor * 2, deviceScaleFactor * 2); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedSurfaceScreenSpaceTransform, child->renderSurface()->screenSpaceTransform()); + + WebTransformationMatrix expectedReplicaDrawTransform; + expectedReplicaDrawTransform.setM22(-1); + expectedReplicaDrawTransform.setM41(6); + expectedReplicaDrawTransform.setM42(6); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedReplicaDrawTransform, child->renderSurface()->replicaDrawTransform()); + + WebTransformationMatrix expectedReplicaScreenSpaceTransform; + expectedReplicaScreenSpaceTransform.setM22(-1); + expectedReplicaScreenSpaceTransform.setM41(6); + expectedReplicaScreenSpaceTransform.setM42(6); + EXPECT_TRANSFORMATION_MATRIX_EQ(expectedReplicaScreenSpaceTransform, child->renderSurface()->replicaScreenSpaceTransform()); +} + +TEST(CCLayerTreeHostCommonTest, verifySubtreeSearch) +{ + RefPtr<LayerChromium> root = LayerChromium::create(); + RefPtr<LayerChromium> child = LayerChromium::create(); + RefPtr<LayerChromium> grandChild = LayerChromium::create(); + RefPtr<LayerChromium> maskLayer = LayerChromium::create(); + RefPtr<LayerChromium> replicaLayer = LayerChromium::create(); + + grandChild->setReplicaLayer(replicaLayer.get()); + child->addChild(grandChild.get()); + child->setMaskLayer(maskLayer.get()); + root->addChild(child.get()); + + int nonexistentId = -1; + EXPECT_EQ(root, CCLayerTreeHostCommon::findLayerInSubtree(root.get(), root->id())); + EXPECT_EQ(child, CCLayerTreeHostCommon::findLayerInSubtree(root.get(), child->id())); + EXPECT_EQ(grandChild, CCLayerTreeHostCommon::findLayerInSubtree(root.get(), grandChild->id())); + EXPECT_EQ(maskLayer, CCLayerTreeHostCommon::findLayerInSubtree(root.get(), maskLayer->id())); + EXPECT_EQ(replicaLayer, CCLayerTreeHostCommon::findLayerInSubtree(root.get(), replicaLayer->id())); + EXPECT_EQ(0, CCLayerTreeHostCommon::findLayerInSubtree(root.get(), nonexistentId)); +} + +} // namespace diff --git a/cc/CCLayerTreeHostImpl.cpp b/cc/CCLayerTreeHostImpl.cpp new file mode 100644 index 0000000..5e90341 --- /dev/null +++ b/cc/CCLayerTreeHostImpl.cpp @@ -0,0 +1,1277 @@ +// 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 "config.h" + +#include "CCLayerTreeHostImpl.h" + +#include "CCActiveGestureAnimation.h" +#include "CCDamageTracker.h" +#include "CCDebugRectHistory.h" +#include "CCDelayBasedTimeSource.h" +#include "CCFontAtlas.h" +#include "CCFrameRateCounter.h" +#include "CCHeadsUpDisplayLayerImpl.h" +#include "CCLayerIterator.h" +#include "CCLayerTreeHost.h" +#include "CCLayerTreeHostCommon.h" +#include "CCMathUtil.h" +#include "CCOverdrawMetrics.h" +#include "CCPageScaleAnimation.h" +#include "CCPrioritizedTextureManager.h" +#include "CCRenderPassDrawQuad.h" +#include "CCRendererGL.h" +#include "CCRenderingStats.h" +#include "CCScrollbarAnimationController.h" +#include "CCScrollbarLayerImpl.h" +#include "CCSettings.h" +#include "CCSingleThreadProxy.h" +#include "TextStream.h" +#include "TraceEvent.h" +#include <wtf/CurrentTime.h> + +using WebKit::WebTransformationMatrix; + +namespace { + +void didVisibilityChange(WebCore::CCLayerTreeHostImpl* id, bool visible) +{ + if (visible) { + TRACE_EVENT_ASYNC_BEGIN1("webkit", "CCLayerTreeHostImpl::setVisible", id, "CCLayerTreeHostImpl", id); + return; + } + + TRACE_EVENT_ASYNC_END0("webkit", "CCLayerTreeHostImpl::setVisible", id); +} + +} // namespace + +namespace WebCore { + +class CCLayerTreeHostImplTimeSourceAdapter : public CCTimeSourceClient { + WTF_MAKE_NONCOPYABLE(CCLayerTreeHostImplTimeSourceAdapter); +public: + static PassOwnPtr<CCLayerTreeHostImplTimeSourceAdapter> create(CCLayerTreeHostImpl* layerTreeHostImpl, PassRefPtr<CCDelayBasedTimeSource> timeSource) + { + return adoptPtr(new CCLayerTreeHostImplTimeSourceAdapter(layerTreeHostImpl, timeSource)); + } + virtual ~CCLayerTreeHostImplTimeSourceAdapter() + { + m_timeSource->setClient(0); + m_timeSource->setActive(false); + } + + virtual void onTimerTick() OVERRIDE + { + // FIXME: We require that animate be called on the impl thread. This + // avoids asserts in single threaded mode. Ideally background ticking + // would be handled by the proxy/scheduler and this could be removed. + DebugScopedSetImplThread impl; + + m_layerTreeHostImpl->animate(monotonicallyIncreasingTime(), currentTime()); + } + + void setActive(bool active) + { + if (active != m_timeSource->active()) + m_timeSource->setActive(active); + } + +private: + CCLayerTreeHostImplTimeSourceAdapter(CCLayerTreeHostImpl* layerTreeHostImpl, PassRefPtr<CCDelayBasedTimeSource> timeSource) + : m_layerTreeHostImpl(layerTreeHostImpl) + , m_timeSource(timeSource) + { + m_timeSource->setClient(this); + } + + CCLayerTreeHostImpl* m_layerTreeHostImpl; + RefPtr<CCDelayBasedTimeSource> m_timeSource; +}; + +PassOwnPtr<CCLayerTreeHostImpl> CCLayerTreeHostImpl::create(const CCLayerTreeSettings& settings, CCLayerTreeHostImplClient* client) +{ + return adoptPtr(new CCLayerTreeHostImpl(settings, client)); +} + +CCLayerTreeHostImpl::CCLayerTreeHostImpl(const CCLayerTreeSettings& settings, CCLayerTreeHostImplClient* client) + : m_client(client) + , m_sourceFrameNumber(-1) + , m_rootScrollLayerImpl(0) + , m_currentlyScrollingLayerImpl(0) + , m_hudLayerImpl(0) + , m_scrollingLayerIdFromPreviousTree(-1) + , m_scrollDeltaIsInScreenSpace(false) + , m_settings(settings) + , m_deviceScaleFactor(1) + , m_visible(true) + , m_contentsTexturesPurged(false) + , m_memoryAllocationLimitBytes(CCPrioritizedTextureManager::defaultMemoryAllocationLimit()) + , m_pageScale(1) + , m_pageScaleDelta(1) + , m_sentPageScaleDelta(1) + , m_minPageScale(0) + , m_maxPageScale(0) + , m_backgroundColor(0) + , m_hasTransparentBackground(false) + , m_needsAnimateLayers(false) + , m_pinchGestureActive(false) + , m_fpsCounter(CCFrameRateCounter::create()) + , m_debugRectHistory(CCDebugRectHistory::create()) +{ + ASSERT(CCProxy::isImplThread()); + didVisibilityChange(this, m_visible); +} + +CCLayerTreeHostImpl::~CCLayerTreeHostImpl() +{ + ASSERT(CCProxy::isImplThread()); + TRACE_EVENT0("cc", "CCLayerTreeHostImpl::~CCLayerTreeHostImpl()"); + + if (m_rootLayerImpl) + clearRenderSurfaces(); +} + +void CCLayerTreeHostImpl::beginCommit() +{ +} + +void CCLayerTreeHostImpl::commitComplete() +{ + TRACE_EVENT0("cc", "CCLayerTreeHostImpl::commitComplete"); + // Recompute max scroll position; must be after layer content bounds are + // updated. + updateMaxScrollPosition(); +} + +bool CCLayerTreeHostImpl::canDraw() +{ + if (!m_rootLayerImpl) { + TRACE_EVENT_INSTANT0("cc", "CCLayerTreeHostImpl::canDraw no root layer"); + return false; + } + if (deviceViewportSize().isEmpty()) { + TRACE_EVENT_INSTANT0("cc", "CCLayerTreeHostImpl::canDraw empty viewport"); + return false; + } + if (!m_renderer) { + TRACE_EVENT_INSTANT0("cc", "CCLayerTreeHostImpl::canDraw no renderer"); + return false; + } + if (m_contentsTexturesPurged) { + TRACE_EVENT_INSTANT0("cc", "CCLayerTreeHostImpl::canDraw contents textures purged"); + return false; + } + return true; +} + +CCGraphicsContext* CCLayerTreeHostImpl::context() const +{ + return m_context.get(); +} + +void CCLayerTreeHostImpl::animate(double monotonicTime, double wallClockTime) +{ + animatePageScale(monotonicTime); + animateLayers(monotonicTime, wallClockTime); + animateGestures(monotonicTime); + animateScrollbars(monotonicTime); +} + +void CCLayerTreeHostImpl::startPageScaleAnimation(const IntSize& targetPosition, bool anchorPoint, float pageScale, double startTime, double duration) +{ + if (!m_rootScrollLayerImpl) + return; + + IntSize scrollTotal = flooredIntSize(m_rootScrollLayerImpl->scrollPosition() + m_rootScrollLayerImpl->scrollDelta()); + scrollTotal.scale(m_pageScaleDelta); + float scaleTotal = m_pageScale * m_pageScaleDelta; + IntSize scaledContentSize = contentSize(); + scaledContentSize.scale(m_pageScaleDelta); + + m_pageScaleAnimation = CCPageScaleAnimation::create(scrollTotal, scaleTotal, m_deviceViewportSize, scaledContentSize, startTime); + + if (anchorPoint) { + IntSize windowAnchor(targetPosition); + windowAnchor.scale(scaleTotal / pageScale); + windowAnchor -= scrollTotal; + m_pageScaleAnimation->zoomWithAnchor(windowAnchor, pageScale, duration); + } else + m_pageScaleAnimation->zoomTo(targetPosition, pageScale, duration); + + m_client->setNeedsRedrawOnImplThread(); + m_client->setNeedsCommitOnImplThread(); +} + +void CCLayerTreeHostImpl::setActiveGestureAnimation(PassOwnPtr<CCActiveGestureAnimation> gestureAnimation) +{ + m_activeGestureAnimation = gestureAnimation; + + if (m_activeGestureAnimation) + m_client->setNeedsRedrawOnImplThread(); +} + +void CCLayerTreeHostImpl::scheduleAnimation() +{ + m_client->setNeedsRedrawOnImplThread(); +} + +void CCLayerTreeHostImpl::trackDamageForAllSurfaces(CCLayerImpl* rootDrawLayer, const CCLayerList& 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) { + CCLayerImpl* renderSurfaceLayer = renderSurfaceLayerList[surfaceIndex]; + CCRenderSurface* renderSurface = renderSurfaceLayer->renderSurface(); + ASSERT(renderSurface); + renderSurface->damageTracker()->updateDamageTrackingState(renderSurface->layerList(), renderSurfaceLayer->id(), renderSurface->surfacePropertyChangedOnlyFromDescendant(), renderSurface->contentRect(), renderSurfaceLayer->maskLayer(), renderSurfaceLayer->filters()); + } +} + +void CCLayerTreeHostImpl::calculateRenderSurfaceLayerList(CCLayerList& renderSurfaceLayerList) +{ + ASSERT(renderSurfaceLayerList.isEmpty()); + ASSERT(m_rootLayerImpl); + ASSERT(m_renderer); // For maxTextureSize. + + { + TRACE_EVENT0("cc", "CCLayerTreeHostImpl::calcDrawEtc"); + CCLayerTreeHostCommon::calculateDrawTransforms(m_rootLayerImpl.get(), deviceViewportSize(), m_deviceScaleFactor, &m_layerSorter, rendererCapabilities().maxTextureSize, renderSurfaceLayerList); + CCLayerTreeHostCommon::calculateVisibleRects(renderSurfaceLayerList); + + trackDamageForAllSurfaces(m_rootLayerImpl.get(), renderSurfaceLayerList); + } +} + +bool CCLayerTreeHostImpl::calculateRenderPasses(FrameData& frame) +{ + ASSERT(frame.renderPasses.isEmpty()); + + calculateRenderSurfaceLayerList(*frame.renderSurfaceLayerList); + + TRACE_EVENT1("cc", "CCLayerTreeHostImpl::calculateRenderPasses", "renderSurfaceLayerList.size()", static_cast<long long unsigned>(frame.renderSurfaceLayerList->size())); + + // Create the render passes in dependency order. + HashMap<CCRenderSurface*, CCRenderPass*> surfacePassMap; + for (int surfaceIndex = frame.renderSurfaceLayerList->size() - 1; surfaceIndex >= 0 ; --surfaceIndex) { + CCLayerImpl* renderSurfaceLayer = (*frame.renderSurfaceLayerList)[surfaceIndex]; + CCRenderSurface* renderSurface = renderSurfaceLayer->renderSurface(); + + int renderPassId = renderSurfaceLayer->id(); + IntRect outputRect = renderSurface->contentRect(); + const WebTransformationMatrix& transformToRootTarget = renderSurface->screenSpaceTransform(); + OwnPtr<CCRenderPass> pass = CCRenderPass::create(renderPassId, outputRect, transformToRootTarget); + pass->setDamageRect(renderSurface->damageTracker()->currentDamageRect()); + pass->setFilters(renderSurfaceLayer->filters()); + pass->setBackgroundFilters(renderSurfaceLayer->backgroundFilters()); + + surfacePassMap.add(renderSurface, pass.get()); + frame.renderPasses.append(pass.get()); + frame.renderPassesById.add(renderPassId, pass.release()); + } + + bool recordMetricsForFrame = true; // FIXME: In the future, disable this when about:tracing is off. + CCOcclusionTrackerImpl 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 CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, CCLayerIteratorActions::FrontToBack> CCLayerIteratorType; + + // 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; + + CCLayerIteratorType end = CCLayerIteratorType::end(frame.renderSurfaceLayerList); + for (CCLayerIteratorType it = CCLayerIteratorType::begin(frame.renderSurfaceLayerList); it != end; ++it) { + CCRenderSurface* renderSurface = it.targetRenderSurfaceLayer()->renderSurface(); + CCRenderPass* pass = surfacePassMap.get(renderSurface); + bool hadMissingTiles = false; + + occlusionTracker.enterLayer(it); + + if (it.representsContributingRenderSurface()) { + CCRenderPass* contributingRenderPass = surfacePassMap.get(it->renderSurface()); + pass->appendQuadsForRenderSurfaceLayer(*it, contributingRenderPass, &occlusionTracker); + } else if (it.representsItself() && !it->visibleContentRect().isEmpty()) { + bool hasOcclusionFromOutsideTargetSurface; + if (occlusionTracker.occluded(*it, it->visibleContentRect(), &hasOcclusionFromOutsideTargetSurface)) { + if (hasOcclusionFromOutsideTargetSurface) + pass->setHasOcclusionFromOutsideTargetSurface(hasOcclusionFromOutsideTargetSurface); + } else { + it->willDraw(m_resourceProvider.get()); + frame.willDrawLayers.append(*it); + pass->appendQuadsForLayer(*it, &occlusionTracker, hadMissingTiles); + } + } + + if (hadMissingTiles) { + bool layerHasAnimatingTransform = it->screenSpaceTransformIsAnimating() || it->drawTransformIsAnimating(); + if (layerHasAnimatingTransform) + drawFrame = false; + } + + occlusionTracker.leaveLayer(it); + } + +#if !ASSERT_DISABLED + for (size_t i = 0; i < frame.renderPasses.size(); ++i) { + for (size_t j = 0; j < frame.renderPasses[i]->quadList().size(); ++j) + ASSERT(frame.renderPasses[i]->quadList()[j]->sharedQuadStateId() >= 0); + } +#endif + + if (!m_hasTransparentBackground) { + frame.renderPasses.last()->setHasTransparentBackground(false); + frame.renderPasses.last()->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 CCLayerTreeHostImpl::animateLayersRecursive(CCLayerImpl* current, double monotonicTime, double wallClockTime, CCAnimationEventsVector* events, bool& didAnimate, bool& needsAnimateLayers) +{ + bool subtreeNeedsAnimateLayers = false; + + CCLayerAnimationController* currentController = current->layerAnimationController(); + + bool hadActiveAnimation = currentController->hasActiveAnimation(); + currentController->animate(monotonicTime, 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].get(), monotonicTime, wallClockTime, events, didAnimate, childNeedsAnimateLayers); + if (childNeedsAnimateLayers) + subtreeNeedsAnimateLayers = true; + } + + needsAnimateLayers = subtreeNeedsAnimateLayers; +} + +void CCLayerTreeHostImpl::setBackgroundTickingEnabled(bool enabled) +{ + // Lazily create the timeSource adapter so that we can vary the interval for testing. + if (!m_timeSourceClientAdapter) + m_timeSourceClientAdapter = CCLayerTreeHostImplTimeSourceAdapter::create(this, CCDelayBasedTimeSource::create(lowFrequencyAnimationInterval(), CCProxy::currentThread())); + + m_timeSourceClientAdapter->setActive(enabled); +} + +IntSize CCLayerTreeHostImpl::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 IntSize(); + return m_rootScrollLayerImpl->children()[0]->contentBounds(); +} + +static inline CCRenderPass* findRenderPassById(int renderPassId, const CCLayerTreeHostImpl::FrameData& frame) +{ + CCRenderPassIdHashMap::const_iterator it = frame.renderPassesById.find(renderPassId); + ASSERT(it != frame.renderPassesById.end()); + return it->second.get(); +} + +static void removeRenderPassesRecursive(int removeRenderPassId, CCLayerTreeHostImpl::FrameData& frame) +{ + CCRenderPass* removeRenderPass = findRenderPassById(removeRenderPassId, frame); + size_t removeIndex = frame.renderPasses.find(removeRenderPass); + + // The pass was already removed by another quad - probably the original, and we are the replica. + if (removeIndex == notFound) + return; + + const CCRenderPass* removedPass = frame.renderPasses[removeIndex]; + frame.renderPasses.remove(removeIndex); + + // Now follow up for all RenderPass quads and remove their RenderPasses recursively. + const CCQuadList& quadList = removedPass->quadList(); + CCQuadList::constBackToFrontIterator quadListIterator = quadList.backToFrontBegin(); + for (; quadListIterator != quadList.backToFrontEnd(); ++quadListIterator) { + CCDrawQuad* currentQuad = (*quadListIterator).get(); + if (currentQuad->material() != CCDrawQuad::RenderPass) + continue; + + int nextRemoveRenderPassId = CCRenderPassDrawQuad::materialCast(currentQuad)->renderPassId(); + removeRenderPassesRecursive(nextRemoveRenderPassId, frame); + } +} + +bool CCLayerTreeHostImpl::CullRenderPassesWithCachedTextures::shouldRemoveRenderPass(const CCRenderPassDrawQuad& quad, const FrameData&) const +{ + return quad.contentsChangedSinceLastFrame().isEmpty() && m_renderer.haveCachedResourcesForRenderPassId(quad.renderPassId()); +} + +bool CCLayerTreeHostImpl::CullRenderPassesWithNoQuads::shouldRemoveRenderPass(const CCRenderPassDrawQuad& quad, const FrameData& frame) const +{ + const CCRenderPass* renderPass = findRenderPassById(quad.renderPassId(), frame); + size_t passIndex = frame.renderPasses.find(renderPass); + + bool renderPassAlreadyRemoved = passIndex == notFound; + if (renderPassAlreadyRemoved) + return false; + + // If any quad or RenderPass draws into this RenderPass, then keep it. + const CCQuadList& quadList = frame.renderPasses[passIndex]->quadList(); + for (CCQuadList::constBackToFrontIterator quadListIterator = quadList.backToFrontBegin(); quadListIterator != quadList.backToFrontEnd(); ++quadListIterator) { + CCDrawQuad* currentQuad = quadListIterator->get(); + + if (currentQuad->material() != CCDrawQuad::RenderPass) + return false; + + const CCRenderPass* contributingPass = findRenderPassById(CCRenderPassDrawQuad::materialCast(currentQuad)->renderPassId(), frame); + if (frame.renderPasses.contains(contributingPass)) + return false; + } + return true; +} + +// Defined for linking tests. +template void CCLayerTreeHostImpl::removeRenderPasses<CCLayerTreeHostImpl::CullRenderPassesWithCachedTextures>(CullRenderPassesWithCachedTextures, FrameData&); +template void CCLayerTreeHostImpl::removeRenderPasses<CCLayerTreeHostImpl::CullRenderPassesWithNoQuads>(CullRenderPassesWithNoQuads, FrameData&); + +// static +template<typename RenderPassCuller> +void CCLayerTreeHostImpl::removeRenderPasses(RenderPassCuller culler, FrameData& frame) +{ + for (size_t it = culler.renderPassListBegin(frame.renderPasses); it != culler.renderPassListEnd(frame.renderPasses); it = culler.renderPassListNext(it)) { + const CCRenderPass* currentPass = frame.renderPasses[it]; + const CCQuadList& quadList = currentPass->quadList(); + CCQuadList::constBackToFrontIterator quadListIterator = quadList.backToFrontBegin(); + + for (; quadListIterator != quadList.backToFrontEnd(); ++quadListIterator) { + CCDrawQuad* currentQuad = quadListIterator->get(); + + if (currentQuad->material() != CCDrawQuad::RenderPass) + continue; + + CCRenderPassDrawQuad* renderPassQuad = static_cast<CCRenderPassDrawQuad*>(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; + ASSERT(it >= 0); + } + } +} + +bool CCLayerTreeHostImpl::prepareToDraw(FrameData& frame) +{ + TRACE_EVENT0("cc", "CCLayerTreeHostImpl::prepareToDraw"); + ASSERT(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 CCLayerTreeHostImpl::releaseContentsTextures() +{ + if (m_contentsTexturesPurged) + return; + m_resourceProvider->deleteOwnedResources(CCRenderer::ContentPool); + m_contentsTexturesPurged = true; + m_client->setNeedsCommitOnImplThread(); +} + +void CCLayerTreeHostImpl::setMemoryAllocationLimitBytes(size_t bytes) +{ + if (m_memoryAllocationLimitBytes == bytes) + return; + m_memoryAllocationLimitBytes = bytes; + + ASSERT(bytes); + m_client->setNeedsCommitOnImplThread(); +} + +void CCLayerTreeHostImpl::onVSyncParametersChanged(double monotonicTimebase, double intervalInSeconds) +{ + m_client->onVSyncParametersChanged(monotonicTimebase, intervalInSeconds); +} + +void CCLayerTreeHostImpl::drawLayers(const FrameData& frame) +{ + TRACE_EVENT0("cc", "CCLayerTreeHostImpl::drawLayers"); + ASSERT(canDraw()); + ASSERT(!frame.renderPasses.isEmpty()); + + // 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(currentTime()); + + 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(FloatRect()); + + // 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 CCLayerTreeHostImpl::didDrawAllLayers(const FrameData& frame) +{ + for (size_t i = 0; i < frame.willDrawLayers.size(); ++i) + frame.willDrawLayers[i]->didDraw(m_resourceProvider.get()); +} + +void CCLayerTreeHostImpl::finishAllRendering() +{ + if (m_renderer) + m_renderer->finish(); +} + +bool CCLayerTreeHostImpl::isContextLost() +{ + return m_renderer && m_renderer->isContextLost(); +} + +const RendererCapabilities& CCLayerTreeHostImpl::rendererCapabilities() const +{ + return m_renderer->capabilities(); +} + +bool CCLayerTreeHostImpl::swapBuffers() +{ + ASSERT(m_renderer); + + m_fpsCounter->markEndOfFrame(); + return m_renderer->swapBuffers(); +} + +void CCLayerTreeHostImpl::didLoseContext() +{ + m_client->didLoseContextOnImplThread(); +} + +void CCLayerTreeHostImpl::onSwapBuffersComplete() +{ + m_client->onSwapBuffersCompleteOnImplThread(); +} + +void CCLayerTreeHostImpl::readback(void* pixels, const IntRect& rect) +{ + ASSERT(m_renderer); + m_renderer->getFramebufferPixels(pixels, rect); +} + +static CCLayerImpl* findRootScrollLayer(CCLayerImpl* layer) +{ + if (!layer) + return 0; + + if (layer->scrollable()) + return layer; + + for (size_t i = 0; i < layer->children().size(); ++i) { + CCLayerImpl* found = findRootScrollLayer(layer->children()[i].get()); + 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 CCLayerImpl* findScrollLayerForContentLayer(CCLayerImpl* layerImpl) +{ + if (!layerImpl) + return 0; + + if (layerImpl->scrollable()) + return layerImpl; + + if (layerImpl->drawsContent() && layerImpl->parent() && layerImpl->parent()->scrollable()) + return layerImpl->parent(); + + return 0; +} + +void CCLayerTreeHostImpl::setRootLayer(PassOwnPtr<CCLayerImpl> layer) +{ + m_rootLayerImpl = layer; + m_rootScrollLayerImpl = findRootScrollLayer(m_rootLayerImpl.get()); + m_currentlyScrollingLayerImpl = 0; + + if (m_rootLayerImpl && m_scrollingLayerIdFromPreviousTree != -1) + m_currentlyScrollingLayerImpl = CCLayerTreeHostCommon::findLayerInSubtree(m_rootLayerImpl.get(), m_scrollingLayerIdFromPreviousTree); + + m_scrollingLayerIdFromPreviousTree = -1; +} + +PassOwnPtr<CCLayerImpl> CCLayerTreeHostImpl::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.release(); +} + +void CCLayerTreeHostImpl::setVisible(bool visible) +{ + ASSERT(CCProxy::isImplThread()); + + if (m_visible == visible) + return; + m_visible = visible; + didVisibilityChange(this, m_visible); + + if (!m_renderer) + return; + + m_renderer->setVisible(visible); + + setBackgroundTickingEnabled(!m_visible && m_needsAnimateLayers); +} + +bool CCLayerTreeHostImpl::initializeRenderer(PassOwnPtr<CCGraphicsContext> context, TextureUploaderOption textureUploader) +{ + if (!context->bindToClient(this)) + return false; + + WebKit::WebGraphicsContext3D* context3d = context->context3D(); + + if (!context3d) { + // FIXME: Implement this path for software compositing. + return false; + } + + OwnPtr<CCGraphicsContext> contextRef(context); + OwnPtr<CCResourceProvider> resourceProvider = CCResourceProvider::create(contextRef.get()); + OwnPtr<CCRendererGL> renderer; + if (resourceProvider.get()) + renderer = CCRendererGL::create(this, resourceProvider.get(), textureUploader); + + // Since we now have a new context/renderer, we cannot continue to use the old + // resources (i.e. renderSurfaces and texture IDs). + if (m_rootLayerImpl) { + clearRenderSurfaces(); + sendDidLoseContextRecursive(m_rootLayerImpl.get()); + } + + m_renderer = renderer.release(); + m_resourceProvider = resourceProvider.release(); + if (m_renderer) + m_context = contextRef.release(); + + if (!m_visible && m_renderer) + m_renderer->setVisible(m_visible); + + return m_renderer; +} + +void CCLayerTreeHostImpl::setViewportSize(const IntSize& layoutViewportSize, const IntSize& deviceViewportSize) +{ + if (layoutViewportSize == m_layoutViewportSize && deviceViewportSize == m_deviceViewportSize) + return; + + m_layoutViewportSize = layoutViewportSize; + m_deviceViewportSize = deviceViewportSize; + + updateMaxScrollPosition(); + + if (m_renderer) + m_renderer->viewportChanged(); +} + +static void adjustScrollsForPageScaleChange(CCLayerImpl* layerImpl, float pageScaleChange) +{ + if (!layerImpl) + return; + + if (layerImpl->scrollable()) { + // We need to convert impl-side scroll deltas to pageScale space. + FloatSize scrollDelta = layerImpl->scrollDelta(); + scrollDelta.scale(pageScaleChange); + layerImpl->setScrollDelta(scrollDelta); + } + + for (size_t i = 0; i < layerImpl->children().size(); ++i) + adjustScrollsForPageScaleChange(layerImpl->children()[i].get(), pageScaleChange); +} + +void CCLayerTreeHostImpl::setDeviceScaleFactor(float deviceScaleFactor) +{ + if (deviceScaleFactor == m_deviceScaleFactor) + return; + m_deviceScaleFactor = deviceScaleFactor; +} + + +void CCLayerTreeHostImpl::setPageScaleFactorAndLimits(float pageScale, float minPageScale, float maxPageScale) +{ + if (!pageScale) + return; + + if (m_sentPageScaleDelta == 1 && pageScale == m_pageScale && minPageScale == m_minPageScale && maxPageScale == m_maxPageScale) + return; + + m_minPageScale = minPageScale; + m_maxPageScale = maxPageScale; + + float pageScaleChange = pageScale / m_pageScale; + m_pageScale = pageScale; + + if (pageScaleChange != 1) + adjustScrollsForPageScaleChange(m_rootScrollLayerImpl, pageScaleChange); + + // Clamp delta to limits and refresh display matrix. + setPageScaleDelta(m_pageScaleDelta / m_sentPageScaleDelta); + m_sentPageScaleDelta = 1; + if (m_rootScrollLayerImpl) + m_rootScrollLayerImpl->setPageScaleDelta(m_pageScaleDelta); +} + +void CCLayerTreeHostImpl::setPageScaleDelta(float delta) +{ + // Clamp to the current min/max limits. + float finalMagnifyScale = m_pageScale * delta; + if (m_minPageScale && finalMagnifyScale < m_minPageScale) + delta = m_minPageScale / m_pageScale; + else if (m_maxPageScale && finalMagnifyScale > m_maxPageScale) + delta = m_maxPageScale / m_pageScale; + + if (delta == m_pageScaleDelta) + return; + + m_pageScaleDelta = delta; + + updateMaxScrollPosition(); + if (m_rootScrollLayerImpl) + m_rootScrollLayerImpl->setPageScaleDelta(m_pageScaleDelta); +} + +void CCLayerTreeHostImpl::updateMaxScrollPosition() +{ + if (!m_rootScrollLayerImpl || !m_rootScrollLayerImpl->children().size()) + return; + + FloatSize viewBounds = m_deviceViewportSize; + if (CCLayerImpl* clipLayer = m_rootScrollLayerImpl->parent()) { + // Compensate for non-overlay scrollbars. + if (clipLayer->masksToBounds()) { + viewBounds = clipLayer->bounds(); + viewBounds.scale(m_deviceScaleFactor); + } + } + viewBounds.scale(1 / m_pageScaleDelta); + + // maxScroll is computed in physical pixels, but scroll positions are in layout pixels. + IntSize maxScroll = contentSize() - expandedIntSize(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.clampNegativeToZero(); + + m_rootScrollLayerImpl->setMaxScrollPosition(maxScroll); +} + +void CCLayerTreeHostImpl::setNeedsRedraw() +{ + m_client->setNeedsRedrawOnImplThread(); +} + +bool CCLayerTreeHostImpl::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(); +} + +CCInputHandlerClient::ScrollStatus CCLayerTreeHostImpl::scrollBegin(const IntPoint& viewportPoint, CCInputHandlerClient::ScrollInputType type) +{ + TRACE_EVENT0("cc", "CCLayerTreeHostImpl::scrollBegin"); + + ASSERT(!m_currentlyScrollingLayerImpl); + clearCurrentlyScrollingLayer(); + + if (!ensureRenderSurfaceLayerList()) + return ScrollIgnored; + + IntPoint deviceViewportPoint = viewportPoint; + deviceViewportPoint.scale(m_deviceScaleFactor, m_deviceScaleFactor); + + // First find out which layer was hit from the saved list of visible layers + // in the most recent frame. + CCLayerImpl* layerImpl = CCLayerTreeHostCommon::findLayerThatIsHitByPoint(viewportPoint, m_renderSurfaceLayerList); + + // Walk up the hierarchy and look for a scrollable layer. + CCLayerImpl* 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) + return ScrollOnMainThread; + + CCLayerImpl* scrollLayerImpl = findScrollLayerForContentLayer(layerImpl); + if (!scrollLayerImpl) + continue; + + ScrollStatus status = scrollLayerImpl->tryScroll(viewportPoint, type); + + // If any layer wants to divert the scroll event to the main thread, abort. + if (status == ScrollOnMainThread) + return ScrollOnMainThread; + + if (status == ScrollStarted && !potentiallyScrollingLayerImpl) + potentiallyScrollingLayerImpl = scrollLayerImpl; + } + + if (potentiallyScrollingLayerImpl) { + m_currentlyScrollingLayerImpl = potentiallyScrollingLayerImpl; + // Gesture events need to be transformed from screen 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_scrollDeltaIsInScreenSpace = (type == Gesture); + return ScrollStarted; + } + return ScrollIgnored; +} + +static FloatSize scrollLayerWithScreenSpaceDelta(CCLayerImpl& layerImpl, const FloatPoint& screenSpacePoint, const FloatSize& screenSpaceDelta) +{ + // Layers with non-invertible screen space transforms should not have passed the scroll hit + // test in the first place. + ASSERT(layerImpl.screenSpaceTransform().isInvertible()); + WebTransformationMatrix inverseScreenSpaceTransform = layerImpl.screenSpaceTransform().inverse(); + + // First project the scroll start and end points to local layer space to find the scroll delta + // in layer coordinates. + bool startClipped, endClipped; + FloatPoint screenSpaceEndPoint = screenSpacePoint + screenSpaceDelta; + FloatPoint localStartPoint = CCMathUtil::projectPoint(inverseScreenSpaceTransform, screenSpacePoint, startClipped); + FloatPoint localEndPoint = CCMathUtil::projectPoint(inverseScreenSpaceTransform, screenSpaceEndPoint, endClipped); + + // In general scroll point coordinates should not get clipped. + ASSERT(!startClipped); + ASSERT(!endClipped); + if (startClipped || endClipped) + return FloatSize(); + + // Apply the scroll delta. + FloatSize previousDelta(layerImpl.scrollDelta()); + layerImpl.scrollBy(localEndPoint - localStartPoint); + + // Calculate the applied scroll delta in screen space coordinates. + FloatPoint actualLocalEndPoint = localStartPoint + layerImpl.scrollDelta() - previousDelta; + FloatPoint actualScreenSpaceEndPoint = CCMathUtil::mapPoint(layerImpl.screenSpaceTransform(), actualLocalEndPoint, endClipped); + ASSERT(!endClipped); + if (endClipped) + return FloatSize(); + return actualScreenSpaceEndPoint - screenSpacePoint; +} + +static FloatSize scrollLayerWithLocalDelta(CCLayerImpl& layerImpl, const FloatSize& localDelta) +{ + FloatSize previousDelta(layerImpl.scrollDelta()); + layerImpl.scrollBy(localDelta); + return layerImpl.scrollDelta() - previousDelta; +} + +void CCLayerTreeHostImpl::scrollBy(const IntPoint& viewportPoint, const IntSize& scrollDelta) +{ + TRACE_EVENT0("cc", "CCLayerTreeHostImpl::scrollBy"); + if (!m_currentlyScrollingLayerImpl) + return; + + FloatSize pendingDelta(scrollDelta); + + pendingDelta.scale(m_deviceScaleFactor); + + for (CCLayerImpl* layerImpl = m_currentlyScrollingLayerImpl; layerImpl; layerImpl = layerImpl->parent()) { + if (!layerImpl->scrollable()) + continue; + + FloatSize appliedDelta; + if (m_scrollDeltaIsInScreenSpace) + appliedDelta = scrollLayerWithScreenSpaceDelta(*layerImpl, 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.1 * 0.1; + if (appliedDelta.diagonalLengthSquared() < 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 (CCMathUtil::smallestAngleBetweenVectors(appliedDelta, pendingDelta) < angleThreshold) { + pendingDelta = FloatSize(); + break; + } + + // Allow further movement only on an axis perpendicular to the direction in which the layer + // moved. + FloatSize perpendicularAxis(-appliedDelta.height(), appliedDelta.width()); + pendingDelta = CCMathUtil::projectVector(pendingDelta, perpendicularAxis); + + if (flooredIntSize(pendingDelta).isZero()) + break; + } + + if (!scrollDelta.isZero() && flooredIntSize(pendingDelta).isEmpty()) { + m_client->setNeedsCommitOnImplThread(); + m_client->setNeedsRedrawOnImplThread(); + } +} + +void CCLayerTreeHostImpl::clearCurrentlyScrollingLayer() +{ + m_currentlyScrollingLayerImpl = 0; + m_scrollingLayerIdFromPreviousTree = -1; +} + +void CCLayerTreeHostImpl::scrollEnd() +{ + clearCurrentlyScrollingLayer(); +} + +void CCLayerTreeHostImpl::pinchGestureBegin() +{ + m_pinchGestureActive = true; + m_previousPinchAnchor = IntPoint(); + + if (m_rootScrollLayerImpl && m_rootScrollLayerImpl->scrollbarAnimationController()) + m_rootScrollLayerImpl->scrollbarAnimationController()->didPinchGestureBegin(); +} + +void CCLayerTreeHostImpl::pinchGestureUpdate(float magnifyDelta, + const IntPoint& anchor) +{ + TRACE_EVENT0("cc", "CCLayerTreeHostImpl::pinchGestureUpdate"); + + if (!m_rootScrollLayerImpl) + return; + + if (m_previousPinchAnchor == IntPoint::zero()) + m_previousPinchAnchor = anchor; + + // Keep the center-of-pinch anchor specified by (x, y) in a stable + // position over the course of the magnify. + FloatPoint previousScaleAnchor(m_previousPinchAnchor.x() / m_pageScaleDelta, m_previousPinchAnchor.y() / m_pageScaleDelta); + setPageScaleDelta(m_pageScaleDelta * magnifyDelta); + FloatPoint newScaleAnchor(anchor.x() / m_pageScaleDelta, anchor.y() / m_pageScaleDelta); + FloatSize move = previousScaleAnchor - newScaleAnchor; + + m_previousPinchAnchor = anchor; + + m_rootScrollLayerImpl->scrollBy(roundedIntSize(move)); + + if (m_rootScrollLayerImpl->scrollbarAnimationController()) + m_rootScrollLayerImpl->scrollbarAnimationController()->didPinchGestureUpdate(); + + m_client->setNeedsCommitOnImplThread(); + m_client->setNeedsRedrawOnImplThread(); +} + +void CCLayerTreeHostImpl::pinchGestureEnd() +{ + m_pinchGestureActive = false; + + if (m_rootScrollLayerImpl && m_rootScrollLayerImpl->scrollbarAnimationController()) + m_rootScrollLayerImpl->scrollbarAnimationController()->didPinchGestureEnd(); + + m_client->setNeedsCommitOnImplThread(); +} + +void CCLayerTreeHostImpl::computeDoubleTapZoomDeltas(CCScrollAndScaleSet* scrollInfo) +{ + float pageScale = m_pageScaleAnimation->finalPageScale(); + IntSize scrollOffset = m_pageScaleAnimation->finalScrollOffset(); + scrollOffset.scale(m_pageScale / pageScale); + makeScrollAndScaleSet(scrollInfo, scrollOffset, pageScale); +} + +void CCLayerTreeHostImpl::computePinchZoomDeltas(CCScrollAndScaleSet* 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.95; + if (m_pageScaleDelta > pinchZoomOutSensitivity) + return; + + // Compute where the scroll offset/page scale would be if fully pinch-zoomed + // out from the anchor point. + IntSize scrollBegin = flooredIntSize(m_rootScrollLayerImpl->scrollPosition() + m_rootScrollLayerImpl->scrollDelta()); + scrollBegin.scale(m_pageScaleDelta); + float scaleBegin = m_pageScale * m_pageScaleDelta; + float pageScaleDeltaToSend = m_minPageScale / m_pageScale; + FloatSize scaledContentsSize = contentSize(); + scaledContentsSize.scale(pageScaleDeltaToSend); + + FloatSize anchor = toSize(m_previousPinchAnchor); + FloatSize scrollEnd = scrollBegin + anchor; + scrollEnd.scale(m_minPageScale / scaleBegin); + scrollEnd -= anchor; + scrollEnd = scrollEnd.shrunkTo(roundedIntSize(scaledContentsSize - m_deviceViewportSize)).expandedTo(FloatSize(0, 0)); + scrollEnd.scale(1 / pageScaleDeltaToSend); + scrollEnd.scale(m_deviceScaleFactor); + + makeScrollAndScaleSet(scrollInfo, roundedIntSize(scrollEnd), m_minPageScale); +} + +void CCLayerTreeHostImpl::makeScrollAndScaleSet(CCScrollAndScaleSet* scrollInfo, const IntSize& scrollOffset, float pageScale) +{ + if (!m_rootScrollLayerImpl) + return; + + CCLayerTreeHostCommon::ScrollUpdateInfo scroll; + scroll.layerId = m_rootScrollLayerImpl->id(); + scroll.scrollDelta = scrollOffset - toSize(m_rootScrollLayerImpl->scrollPosition()); + scrollInfo->scrolls.append(scroll); + m_rootScrollLayerImpl->setSentScrollDelta(scroll.scrollDelta); + m_sentPageScaleDelta = scrollInfo->pageScaleDelta = pageScale / m_pageScale; +} + +static void collectScrollDeltas(CCScrollAndScaleSet* scrollInfo, CCLayerImpl* layerImpl) +{ + if (!layerImpl) + return; + + if (!layerImpl->scrollDelta().isZero()) { + IntSize scrollDelta = flooredIntSize(layerImpl->scrollDelta()); + CCLayerTreeHostCommon::ScrollUpdateInfo scroll; + scroll.layerId = layerImpl->id(); + scroll.scrollDelta = scrollDelta; + scrollInfo->scrolls.append(scroll); + layerImpl->setSentScrollDelta(scrollDelta); + } + + for (size_t i = 0; i < layerImpl->children().size(); ++i) + collectScrollDeltas(scrollInfo, layerImpl->children()[i].get()); +} + +PassOwnPtr<CCScrollAndScaleSet> CCLayerTreeHostImpl::processScrollDeltas() +{ + OwnPtr<CCScrollAndScaleSet> scrollInfo = adoptPtr(new CCScrollAndScaleSet()); + + if (m_pinchGestureActive || m_pageScaleAnimation) { + m_sentPageScaleDelta = scrollInfo->pageScaleDelta = 1; + if (m_pinchGestureActive) + computePinchZoomDeltas(scrollInfo.get()); + else if (m_pageScaleAnimation.get()) + computeDoubleTapZoomDeltas(scrollInfo.get()); + return scrollInfo.release(); + } + + collectScrollDeltas(scrollInfo.get(), m_rootLayerImpl.get()); + m_sentPageScaleDelta = scrollInfo->pageScaleDelta = m_pageScaleDelta; + + return scrollInfo.release(); +} + +void CCLayerTreeHostImpl::setFullRootLayerDamage() +{ + if (m_rootLayerImpl) { + CCRenderSurface* renderSurface = m_rootLayerImpl->renderSurface(); + if (renderSurface) + renderSurface->damageTracker()->forceFullDamageNextUpdate(); + } +} + +void CCLayerTreeHostImpl::animatePageScale(double monotonicTime) +{ + if (!m_pageScaleAnimation || !m_rootScrollLayerImpl) + return; + + IntSize scrollTotal = flooredIntSize(m_rootScrollLayerImpl->scrollPosition() + m_rootScrollLayerImpl->scrollDelta()); + + setPageScaleDelta(m_pageScaleAnimation->pageScaleAtTime(monotonicTime) / m_pageScale); + IntSize nextScroll = m_pageScaleAnimation->scrollOffsetAtTime(monotonicTime); + nextScroll.scale(1 / m_pageScaleDelta); + m_rootScrollLayerImpl->scrollBy(nextScroll - scrollTotal); + m_client->setNeedsRedrawOnImplThread(); + + if (m_pageScaleAnimation->isAnimationCompleteAtTime(monotonicTime)) { + m_pageScaleAnimation.clear(); + m_client->setNeedsCommitOnImplThread(); + } +} + +void CCLayerTreeHostImpl::animateLayers(double monotonicTime, double wallClockTime) +{ + if (!CCSettings::acceleratedAnimationEnabled() || !m_needsAnimateLayers || !m_rootLayerImpl) + return; + + TRACE_EVENT0("cc", "CCLayerTreeHostImpl::animateLayers"); + + OwnPtr<CCAnimationEventsVector> events(adoptPtr(new CCAnimationEventsVector)); + + bool didAnimate = false; + animateLayersRecursive(m_rootLayerImpl.get(), monotonicTime, wallClockTime, events.get(), didAnimate, m_needsAnimateLayers); + + if (!events->isEmpty()) + m_client->postAnimationEventsToMainThreadOnImplThread(events.release(), wallClockTime); + + if (didAnimate) + m_client->setNeedsRedrawOnImplThread(); + + setBackgroundTickingEnabled(!m_visible && m_needsAnimateLayers); +} + +double CCLayerTreeHostImpl::lowFrequencyAnimationInterval() const +{ + return 1; +} + +void CCLayerTreeHostImpl::sendDidLoseContextRecursive(CCLayerImpl* current) +{ + ASSERT(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].get()); +} + +static void clearRenderSurfacesOnCCLayerImplRecursive(CCLayerImpl* current) +{ + ASSERT(current); + for (size_t i = 0; i < current->children().size(); ++i) + clearRenderSurfacesOnCCLayerImplRecursive(current->children()[i].get()); + current->clearRenderSurface(); +} + +void CCLayerTreeHostImpl::clearRenderSurfaces() +{ + clearRenderSurfacesOnCCLayerImplRecursive(m_rootLayerImpl.get()); + m_renderSurfaceLayerList.clear(); +} + +String CCLayerTreeHostImpl::layerTreeAsText() const +{ + TextStream ts; + if (m_rootLayerImpl) { + ts << m_rootLayerImpl->layerTreeAsText(); + ts << "RenderSurfaces:\n"; + dumpRenderSurfaces(ts, 1, m_rootLayerImpl.get()); + } + return ts.release(); +} + +void CCLayerTreeHostImpl::dumpRenderSurfaces(TextStream& ts, int indent, const CCLayerImpl* layer) const +{ + if (layer->renderSurface()) + layer->renderSurface()->dumpSurface(ts, indent); + + for (size_t i = 0; i < layer->children().size(); ++i) + dumpRenderSurfaces(ts, indent, layer->children()[i].get()); +} + + +void CCLayerTreeHostImpl::animateGestures(double monotonicTime) +{ + if (!m_activeGestureAnimation) + return; + + bool isContinuing = m_activeGestureAnimation->animate(monotonicTime); + if (isContinuing) + m_client->setNeedsRedrawOnImplThread(); + else + m_activeGestureAnimation.clear(); +} + +int CCLayerTreeHostImpl::sourceAnimationFrameNumber() const +{ + return fpsCounter()->currentFrameNumber(); +} + +void CCLayerTreeHostImpl::renderingStats(CCRenderingStats& stats) const +{ + stats.numFramesSentToScreen = fpsCounter()->currentFrameNumber(); + stats.droppedFrameCount = fpsCounter()->droppedFrameCount(); +} + +void CCLayerTreeHostImpl::animateScrollbars(double monotonicTime) +{ + animateScrollbarsRecursive(m_rootLayerImpl.get(), monotonicTime); +} + +void CCLayerTreeHostImpl::animateScrollbarsRecursive(CCLayerImpl* layer, double monotonicTime) +{ + if (!layer) + return; + + CCScrollbarAnimationController* scrollbarController = layer->scrollbarAnimationController(); + if (scrollbarController && scrollbarController->animate(monotonicTime)) + m_client->setNeedsRedrawOnImplThread(); + + for (size_t i = 0; i < layer->children().size(); ++i) + animateScrollbarsRecursive(layer->children()[i].get(), monotonicTime); +} + +} // namespace WebCore diff --git a/cc/CCLayerTreeHostImpl.h b/cc/CCLayerTreeHostImpl.h new file mode 100644 index 0000000..f3e93dd --- /dev/null +++ b/cc/CCLayerTreeHostImpl.h @@ -0,0 +1,295 @@ +// 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. + +#ifndef CCLayerTreeHostImpl_h +#define CCLayerTreeHostImpl_h + +#include "CCAnimationEvents.h" +#include "CCInputHandler.h" +#include "CCLayerSorter.h" +#include "CCRenderPass.h" +#include "CCRenderer.h" +#include "SkColor.h" +#include <public/WebCompositorOutputSurfaceClient.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class CCActiveGestureAnimation; +class CCCompletionEvent; +class CCDebugRectHistory; +class CCFrameRateCounter; +class CCHeadsUpDisplayLayerImpl; +class CCLayerImpl; +class CCLayerTreeHostImplTimeSourceAdapter; +class CCPageScaleAnimation; +class CCRenderPassDrawQuad; +class CCResourceProvider; +struct RendererCapabilities; +struct CCRenderingStats; + +// CCLayerTreeHost->CCProxy callback interface. +class CCLayerTreeHostImplClient { +public: + virtual void didLoseContextOnImplThread() = 0; + virtual void onSwapBuffersCompleteOnImplThread() = 0; + virtual void onVSyncParametersChanged(double monotonicTimebase, double intervalInSeconds) = 0; + virtual void setNeedsRedrawOnImplThread() = 0; + virtual void setNeedsCommitOnImplThread() = 0; + virtual void postAnimationEventsToMainThreadOnImplThread(PassOwnPtr<CCAnimationEventsVector>, double wallClockTime) = 0; +}; + +// CCLayerTreeHostImpl owns the CCLayerImpl tree as well as associated rendering state +class CCLayerTreeHostImpl : public CCInputHandlerClient, + public CCRendererClient, + public WebKit::WebCompositorOutputSurfaceClient { + WTF_MAKE_NONCOPYABLE(CCLayerTreeHostImpl); + typedef Vector<CCLayerImpl*> CCLayerList; + +public: + static PassOwnPtr<CCLayerTreeHostImpl> create(const CCLayerTreeSettings&, CCLayerTreeHostImplClient*); + virtual ~CCLayerTreeHostImpl(); + + // CCInputHandlerClient implementation + virtual CCInputHandlerClient::ScrollStatus scrollBegin(const IntPoint&, CCInputHandlerClient::ScrollInputType) OVERRIDE; + virtual void scrollBy(const IntPoint&, const IntSize&) OVERRIDE; + virtual void scrollEnd() OVERRIDE; + virtual void pinchGestureBegin() OVERRIDE; + virtual void pinchGestureUpdate(float, const IntPoint&) OVERRIDE; + virtual void pinchGestureEnd() OVERRIDE; + virtual void startPageScaleAnimation(const IntSize& targetPosition, bool anchorPoint, float pageScale, double startTime, double duration) OVERRIDE; + virtual CCActiveGestureAnimation* activeGestureAnimation() OVERRIDE { return m_activeGestureAnimation.get(); } + // To clear an active animation, pass nullptr. + virtual void setActiveGestureAnimation(PassOwnPtr<CCActiveGestureAnimation>) OVERRIDE; + virtual void scheduleAnimation() OVERRIDE; + + struct FrameData { + Vector<IntRect> occludingScreenSpaceRects; + CCRenderPassList renderPasses; + CCRenderPassIdHashMap renderPassesById; + CCLayerList* renderSurfaceLayerList; + CCLayerList willDrawLayers; + }; + + // Virtual for testing. + virtual void beginCommit(); + virtual void commitComplete(); + virtual void animate(double monotonicTime, double wallClockTime); + + // Returns false if problems occured preparing the frame, and we should try + // to avoid displaying the frame. If prepareToDraw is called, + // didDrawAllLayers must also be called, regardless of whether drawLayers is + // called between the two. + virtual bool prepareToDraw(FrameData&); + virtual void drawLayers(const FrameData&); + // Must be called if and only if prepareToDraw was called. + void didDrawAllLayers(const FrameData&); + + // CCRendererClient implementation + virtual const IntSize& deviceViewportSize() const OVERRIDE { return m_deviceViewportSize; } + virtual const CCLayerTreeSettings& settings() const OVERRIDE { return m_settings; } + virtual void didLoseContext() OVERRIDE; + virtual void onSwapBuffersComplete() OVERRIDE; + virtual void setFullRootLayerDamage() OVERRIDE; + virtual void releaseContentsTextures() OVERRIDE; + virtual void setMemoryAllocationLimitBytes(size_t) OVERRIDE; + + // WebCompositorOutputSurfaceClient implementation. + virtual void onVSyncParametersChanged(double monotonicTimebase, double intervalInSeconds) OVERRIDE; + + // Implementation + bool canDraw(); + CCGraphicsContext* context() const; + + String layerTreeAsText() const; + + void finishAllRendering(); + int sourceAnimationFrameNumber() const; + + bool initializeRenderer(PassOwnPtr<CCGraphicsContext>, TextureUploaderOption); + bool isContextLost(); + CCRenderer* renderer() { return m_renderer.get(); } + const RendererCapabilities& rendererCapabilities() const; + + bool swapBuffers(); + + void readback(void* pixels, const IntRect&); + + void setRootLayer(PassOwnPtr<CCLayerImpl>); + CCLayerImpl* rootLayer() { return m_rootLayerImpl.get(); } + + void setHudLayer(CCHeadsUpDisplayLayerImpl* layerImpl) { m_hudLayerImpl = layerImpl; } + CCHeadsUpDisplayLayerImpl* hudLayer() { return m_hudLayerImpl; } + + // Release ownership of the current layer tree and replace it with an empty + // tree. Returns the root layer of the detached tree. + PassOwnPtr<CCLayerImpl> detachLayerTree(); + + CCLayerImpl* rootScrollLayer() const { return m_rootScrollLayerImpl; } + + bool visible() const { return m_visible; } + void setVisible(bool); + + int sourceFrameNumber() const { return m_sourceFrameNumber; } + void setSourceFrameNumber(int frameNumber) { m_sourceFrameNumber = frameNumber; } + + bool contentsTexturesPurged() const { return m_contentsTexturesPurged; } + void resetContentsTexturesPurged() { m_contentsTexturesPurged = false; } + size_t memoryAllocationLimitBytes() const { return m_memoryAllocationLimitBytes; } + + void setViewportSize(const IntSize& layoutViewportSize, const IntSize& deviceViewportSize); + const IntSize& layoutViewportSize() const { return m_layoutViewportSize; } + + float deviceScaleFactor() const { return m_deviceScaleFactor; } + void setDeviceScaleFactor(float); + + float pageScale() const { return m_pageScale; } + void setPageScaleFactorAndLimits(float pageScale, float minPageScale, float maxPageScale); + + PassOwnPtr<CCScrollAndScaleSet> processScrollDeltas(); + + void startPageScaleAnimation(const IntSize& tragetPosition, bool useAnchor, float scale, double durationSec); + + SkColor backgroundColor() const { return m_backgroundColor; } + void setBackgroundColor(SkColor color) { m_backgroundColor = color; } + + bool hasTransparentBackground() const { return m_hasTransparentBackground; } + void setHasTransparentBackground(bool transparent) { m_hasTransparentBackground = transparent; } + + bool needsAnimateLayers() const { return m_needsAnimateLayers; } + void setNeedsAnimateLayers() { m_needsAnimateLayers = true; } + + void setNeedsRedraw(); + + void renderingStats(CCRenderingStats&) const; + + CCFrameRateCounter* fpsCounter() const { return m_fpsCounter.get(); } + CCDebugRectHistory* debugRectHistory() const { return m_debugRectHistory.get(); } + CCResourceProvider* resourceProvider() const { return m_resourceProvider.get(); } + + class CullRenderPassesWithCachedTextures { + public: + bool shouldRemoveRenderPass(const CCRenderPassDrawQuad&, const FrameData&) const; + + // Iterates from the root first, in order to remove the surfaces closest + // to the root with cached textures, and all surfaces that draw into + // them. + size_t renderPassListBegin(const CCRenderPassList& list) const { return list.size() - 1; } + size_t renderPassListEnd(const CCRenderPassList&) const { return 0 - 1; } + size_t renderPassListNext(size_t it) const { return it - 1; } + + CullRenderPassesWithCachedTextures(CCRenderer& renderer) : m_renderer(renderer) { } + private: + CCRenderer& m_renderer; + }; + + class CullRenderPassesWithNoQuads { + public: + bool shouldRemoveRenderPass(const CCRenderPassDrawQuad&, const FrameData&) const; + + // Iterates in draw order, so that when a surface is removed, and its + // target becomes empty, then its target can be removed also. + size_t renderPassListBegin(const CCRenderPassList&) const { return 0; } + size_t renderPassListEnd(const CCRenderPassList& list) const { return list.size(); } + size_t renderPassListNext(size_t it) const { return it + 1; } + }; + + template<typename RenderPassCuller> + static void removeRenderPasses(RenderPassCuller, FrameData&); + +protected: + CCLayerTreeHostImpl(const CCLayerTreeSettings&, CCLayerTreeHostImplClient*); + + void animatePageScale(double monotonicTime); + void animateGestures(double monotonicTime); + void animateScrollbars(double monotonicTime); + + // Exposed for testing. + void calculateRenderSurfaceLayerList(CCLayerList&); + + // Virtual for testing. + virtual void animateLayers(double monotonicTime, double wallClockTime); + + // Virtual for testing. Measured in seconds. + virtual double lowFrequencyAnimationInterval() const; + + CCLayerTreeHostImplClient* m_client; + int m_sourceFrameNumber; + +private: + void computeDoubleTapZoomDeltas(CCScrollAndScaleSet* scrollInfo); + void computePinchZoomDeltas(CCScrollAndScaleSet* scrollInfo); + void makeScrollAndScaleSet(CCScrollAndScaleSet* scrollInfo, const IntSize& scrollOffset, float pageScale); + + void setPageScaleDelta(float); + void updateMaxScrollPosition(); + void trackDamageForAllSurfaces(CCLayerImpl* rootDrawLayer, const CCLayerList& renderSurfaceLayerList); + + // Returns false if the frame should not be displayed. This function should + // only be called from prepareToDraw, as didDrawAllLayers must be called + // if this helper function is called. + bool calculateRenderPasses(FrameData&); + void animateLayersRecursive(CCLayerImpl*, double monotonicTime, double wallClockTime, CCAnimationEventsVector*, bool& didAnimate, bool& needsAnimateLayers); + void setBackgroundTickingEnabled(bool); + IntSize contentSize() const; + + void sendDidLoseContextRecursive(CCLayerImpl*); + void clearRenderSurfaces(); + bool ensureRenderSurfaceLayerList(); + void clearCurrentlyScrollingLayer(); + + void animateScrollbarsRecursive(CCLayerImpl*, double monotonicTime); + + void dumpRenderSurfaces(TextStream&, int indent, const CCLayerImpl*) const; + + OwnPtr<CCGraphicsContext> m_context; + OwnPtr<CCResourceProvider> m_resourceProvider; + OwnPtr<CCRenderer> m_renderer; + OwnPtr<CCLayerImpl> m_rootLayerImpl; + CCLayerImpl* m_rootScrollLayerImpl; + CCLayerImpl* m_currentlyScrollingLayerImpl; + CCHeadsUpDisplayLayerImpl* m_hudLayerImpl; + int m_scrollingLayerIdFromPreviousTree; + bool m_scrollDeltaIsInScreenSpace; + CCLayerTreeSettings m_settings; + IntSize m_layoutViewportSize; + IntSize m_deviceViewportSize; + float m_deviceScaleFactor; + bool m_visible; + bool m_contentsTexturesPurged; + size_t m_memoryAllocationLimitBytes; + + float m_pageScale; + float m_pageScaleDelta; + float m_sentPageScaleDelta; + float m_minPageScale, m_maxPageScale; + + SkColor m_backgroundColor; + bool m_hasTransparentBackground; + + // If this is true, it is necessary to traverse the layer tree ticking the animators. + bool m_needsAnimateLayers; + bool m_pinchGestureActive; + IntPoint m_previousPinchAnchor; + + OwnPtr<CCPageScaleAnimation> m_pageScaleAnimation; + OwnPtr<CCActiveGestureAnimation> m_activeGestureAnimation; + + // This is used for ticking animations slowly when hidden. + OwnPtr<CCLayerTreeHostImplTimeSourceAdapter> m_timeSourceClientAdapter; + + CCLayerSorter m_layerSorter; + + // List of visible layers for the most recently prepared frame. Used for + // rendering and input event hit testing. + CCLayerList m_renderSurfaceLayerList; + + OwnPtr<CCFrameRateCounter> m_fpsCounter; + OwnPtr<CCDebugRectHistory> m_debugRectHistory; +}; + +}; + +#endif diff --git a/cc/CCLayerTreeHostImplTest.cpp b/cc/CCLayerTreeHostImplTest.cpp new file mode 100644 index 0000000..ebbcf8f --- /dev/null +++ b/cc/CCLayerTreeHostImplTest.cpp @@ -0,0 +1,4117 @@ +// 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 "config.h" + +#include "CCLayerTreeHostImpl.h" + +#include "CCAnimationTestCommon.h" +#include "CCHeadsUpDisplayLayerImpl.h" +#include "CCIOSurfaceLayerImpl.h" +#include "CCLayerImpl.h" +#include "CCLayerTestCommon.h" +#include "CCLayerTilingData.h" +#include "CCLayerTreeTestCommon.h" +#include "CCQuadCuller.h" +#include "CCRenderPassDrawQuad.h" +#include "CCRendererGL.h" +#include "CCScrollbarLayerImpl.h" +#include "CCSettings.h" +#include "CCSingleThreadProxy.h" +#include "CCSolidColorDrawQuad.h" +#include "CCTestCommon.h" +#include "CCTextureLayerImpl.h" +#include "CCTileDrawQuad.h" +#include "CCTiledLayerImpl.h" +#include "CCVideoLayerImpl.h" +#include "FakeWebCompositorOutputSurface.h" +#include "FakeWebGraphicsContext3D.h" +#include "FakeWebScrollbarThemeGeometry.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/WebVideoFrame.h> +#include <public/WebVideoFrameProvider.h> + +using namespace CCLayerTestCommon; +using namespace WebCore; +using namespace WebKit; +using namespace WebKitTests; + +using ::testing::Mock; +using ::testing::Return; +using ::testing::AnyNumber; +using ::testing::AtLeast; +using ::testing::_; + +namespace { + +class CCLayerTreeHostImplTest : public testing::Test, public CCLayerTreeHostImplClient { +public: + CCLayerTreeHostImplTest() + : m_didRequestCommit(false) + , m_didRequestRedraw(false) + { + CCLayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = IntSize(); + + m_hostImpl = CCLayerTreeHostImpl::create(settings, this); + m_hostImpl->initializeRenderer(createContext(), UnthrottledUploader); + m_hostImpl->setViewportSize(IntSize(10, 10), IntSize(10, 10)); + } + + virtual void didLoseContextOnImplThread() OVERRIDE { } + virtual void onSwapBuffersCompleteOnImplThread() OVERRIDE { } + virtual void onVSyncParametersChanged(double, double) OVERRIDE { } + virtual void setNeedsRedrawOnImplThread() OVERRIDE { m_didRequestRedraw = true; } + virtual void setNeedsCommitOnImplThread() OVERRIDE { m_didRequestCommit = true; } + virtual void postAnimationEventsToMainThreadOnImplThread(PassOwnPtr<CCAnimationEventsVector>, double wallClockTime) OVERRIDE { } + + PassOwnPtr<CCLayerTreeHostImpl> createLayerTreeHost(bool partialSwap, PassOwnPtr<CCGraphicsContext> graphicsContext, PassOwnPtr<CCLayerImpl> rootPtr) + { + CCSettings::setPartialSwapEnabled(partialSwap); + + CCLayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = IntSize(); + + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + myHostImpl->initializeRenderer(graphicsContext, UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(10, 10), IntSize(10, 10)); + + OwnPtr<CCLayerImpl> root = rootPtr; + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(0, 0)); + root->setBounds(IntSize(10, 10)); + root->setContentBounds(IntSize(10, 10)); + root->setVisibleContentRect(IntRect(0, 0, 10, 10)); + root->setDrawsContent(true); + myHostImpl->setRootLayer(root.release()); + return myHostImpl.release(); + } + + static void expectClearedScrollDeltasRecursive(CCLayerImpl* layer) + { + ASSERT_EQ(layer->scrollDelta(), IntSize()); + for (size_t i = 0; i < layer->children().size(); ++i) + expectClearedScrollDeltasRecursive(layer->children()[i].get()); + } + + static void expectContains(const CCScrollAndScaleSet& scrollInfo, int id, const IntSize& scrollDelta) + { + int timesEncountered = 0; + + for (size_t i = 0; i < scrollInfo.scrolls.size(); ++i) { + if (scrollInfo.scrolls[i].layerId != id) + continue; + EXPECT_EQ(scrollDelta.width(), scrollInfo.scrolls[i].scrollDelta.width()); + EXPECT_EQ(scrollDelta.height(), scrollInfo.scrolls[i].scrollDelta.height()); + timesEncountered++; + } + + ASSERT_EQ(timesEncountered, 1); + } + + void setupScrollAndContentsLayers(const IntSize& contentSize) + { + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + root->setScrollable(true); + root->setScrollPosition(IntPoint(0, 0)); + root->setMaxScrollPosition(contentSize); + root->setBounds(contentSize); + root->setContentBounds(contentSize); + root->setPosition(FloatPoint(0, 0)); + root->setAnchorPoint(FloatPoint(0, 0)); + + OwnPtr<CCLayerImpl> contents = CCLayerImpl::create(2); + contents->setDrawsContent(true); + contents->setBounds(contentSize); + contents->setContentBounds(contentSize); + contents->setPosition(FloatPoint(0, 0)); + contents->setAnchorPoint(FloatPoint(0, 0)); + root->addChild(contents.release()); + m_hostImpl->setRootLayer(root.release()); + } + + static PassOwnPtr<CCLayerImpl> createScrollableLayer(int id, const IntSize& size) + { + OwnPtr<CCLayerImpl> layer = CCLayerImpl::create(id); + layer->setScrollable(true); + layer->setDrawsContent(true); + layer->setBounds(size); + layer->setContentBounds(size); + layer->setMaxScrollPosition(IntSize(size.width() * 2, size.height() * 2)); + return layer.release(); + } + + void initializeRendererAndDrawFrame() + { + m_hostImpl->initializeRenderer(createContext(), UnthrottledUploader); + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + } + +protected: + PassOwnPtr<CCGraphicsContext> createContext() + { + return FakeWebCompositorOutputSurface::create(adoptPtr(new FakeWebGraphicsContext3D)); + } + + DebugScopedSetImplThread m_alwaysImplThread; + DebugScopedSetMainThreadBlocked m_alwaysMainThreadBlocked; + + OwnPtr<CCLayerTreeHostImpl> m_hostImpl; + bool m_didRequestCommit; + bool m_didRequestRedraw; + CCScopedSettings m_scopedSettings; +}; + +class FakeWebGraphicsContext3DMakeCurrentFails : public FakeWebGraphicsContext3D { +public: + virtual bool makeContextCurrent() { return false; } +}; + +TEST_F(CCLayerTreeHostImplTest, scrollDeltaNoLayers) +{ + ASSERT_FALSE(m_hostImpl->rootLayer()); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + ASSERT_EQ(scrollInfo->scrolls.size(), 0u); +} + +TEST_F(CCLayerTreeHostImplTest, scrollDeltaTreeButNoChanges) +{ + { + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + root->addChild(CCLayerImpl::create(2)); + root->addChild(CCLayerImpl::create(3)); + root->children()[1]->addChild(CCLayerImpl::create(4)); + root->children()[1]->addChild(CCLayerImpl::create(5)); + root->children()[1]->children()[0]->addChild(CCLayerImpl::create(6)); + m_hostImpl->setRootLayer(root.release()); + } + CCLayerImpl* root = m_hostImpl->rootLayer(); + + expectClearedScrollDeltasRecursive(root); + + OwnPtr<CCScrollAndScaleSet> scrollInfo; + + scrollInfo = m_hostImpl->processScrollDeltas(); + ASSERT_EQ(scrollInfo->scrolls.size(), 0u); + expectClearedScrollDeltasRecursive(root); + + scrollInfo = m_hostImpl->processScrollDeltas(); + ASSERT_EQ(scrollInfo->scrolls.size(), 0u); + expectClearedScrollDeltasRecursive(root); +} + +TEST_F(CCLayerTreeHostImplTest, scrollDeltaRepeatedScrolls) +{ + IntPoint scrollPosition(20, 30); + IntSize scrollDelta(11, -15); + { + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + root->setScrollPosition(scrollPosition); + root->setScrollable(true); + root->setMaxScrollPosition(IntSize(100, 100)); + root->scrollBy(scrollDelta); + m_hostImpl->setRootLayer(root.release()); + } + CCLayerImpl* root = m_hostImpl->rootLayer(); + + OwnPtr<CCScrollAndScaleSet> scrollInfo; + + scrollInfo = m_hostImpl->processScrollDeltas(); + ASSERT_EQ(scrollInfo->scrolls.size(), 1u); + EXPECT_EQ(root->sentScrollDelta(), scrollDelta); + expectContains(*scrollInfo, root->id(), scrollDelta); + + IntSize scrollDelta2(-5, 27); + root->scrollBy(scrollDelta2); + scrollInfo = m_hostImpl->processScrollDeltas(); + ASSERT_EQ(scrollInfo->scrolls.size(), 1u); + EXPECT_EQ(root->sentScrollDelta(), scrollDelta + scrollDelta2); + expectContains(*scrollInfo, root->id(), scrollDelta + scrollDelta2); + + root->scrollBy(IntSize()); + scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(root->sentScrollDelta(), scrollDelta + scrollDelta2); +} + +TEST_F(CCLayerTreeHostImplTest, scrollRootCallsCommitAndRedraw) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50), IntSize(50, 50)); + initializeRendererAndDrawFrame(); + + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), IntSize(0, 10)); + m_hostImpl->scrollEnd(); + EXPECT_TRUE(m_didRequestRedraw); + EXPECT_TRUE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollWithoutRootLayer) +{ + // We should not crash when trying to scroll an empty layer tree. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollIgnored); +} + +TEST_F(CCLayerTreeHostImplTest, scrollWithoutRenderer) +{ + CCLayerTreeSettings settings; + m_hostImpl = CCLayerTreeHostImpl::create(settings, this); + + // Initialization will fail here. + m_hostImpl->initializeRenderer(FakeWebCompositorOutputSurface::create(adoptPtr(new FakeWebGraphicsContext3DMakeCurrentFails)), UnthrottledUploader); + m_hostImpl->setViewportSize(IntSize(10, 10), IntSize(10, 10)); + + setupScrollAndContentsLayers(IntSize(100, 100)); + + // We should not crash when trying to scroll after the renderer initialization fails. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollIgnored); +} + +TEST_F(CCLayerTreeHostImplTest, replaceTreeWhileScrolling) +{ + const int scrollLayerId = 1; + + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50), IntSize(50, 50)); + initializeRendererAndDrawFrame(); + + // We should not crash if the tree is replaced while we are scrolling. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->detachLayerTree(); + + setupScrollAndContentsLayers(IntSize(100, 100)); + + // We should still be scrolling, because the scrolled layer also exists in the new tree. + IntSize scrollDelta(0, 10); + m_hostImpl->scrollBy(IntPoint(), scrollDelta); + m_hostImpl->scrollEnd(); + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo, scrollLayerId, scrollDelta); +} + +TEST_F(CCLayerTreeHostImplTest, clearRootRenderSurfaceAndScroll) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50), IntSize(50, 50)); + initializeRendererAndDrawFrame(); + + // We should be able to scroll even if the root layer loses its render surface after the most + // recent render. + m_hostImpl->rootLayer()->clearRenderSurface(); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); +} + +TEST_F(CCLayerTreeHostImplTest, wheelEventHandlers) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50), IntSize(50, 50)); + initializeRendererAndDrawFrame(); + CCLayerImpl* root = m_hostImpl->rootLayer(); + + root->setHaveWheelEventHandlers(true); + + // With registered event handlers, wheel scrolls have to go to the main thread. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread); + + // But gesture scrolls can still be handled. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted); +} + +TEST_F(CCLayerTreeHostImplTest, shouldScrollOnMainThread) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50), IntSize(50, 50)); + initializeRendererAndDrawFrame(); + CCLayerImpl* root = m_hostImpl->rootLayer(); + + root->setShouldScrollOnMainThread(true); + + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollOnMainThread); +} + +TEST_F(CCLayerTreeHostImplTest, nonFastScrollableRegionBasic) +{ + setupScrollAndContentsLayers(IntSize(200, 200)); + m_hostImpl->setViewportSize(IntSize(100, 100), IntSize(100, 100)); + initializeRendererAndDrawFrame(); + CCLayerImpl* root = m_hostImpl->rootLayer(); + + root->setNonFastScrollableRegion(IntRect(0, 0, 50, 50)); + + // All scroll types inside the non-fast scrollable region should fail. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(25, 25), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(25, 25), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollOnMainThread); + + // All scroll types outside this region should succeed. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(75, 75), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), IntSize(0, 10)); + m_hostImpl->scrollEnd(); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(75, 75), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), IntSize(0, 10)); + m_hostImpl->scrollEnd(); +} + +TEST_F(CCLayerTreeHostImplTest, nonFastScrollableRegionWithOffset) +{ + setupScrollAndContentsLayers(IntSize(200, 200)); + m_hostImpl->setViewportSize(IntSize(100, 100), IntSize(100, 100)); + CCLayerImpl* root = m_hostImpl->rootLayer(); + + root->setNonFastScrollableRegion(IntRect(0, 0, 50, 50)); + root->setPosition(FloatPoint(-25, 0)); + initializeRendererAndDrawFrame(); + + // This point would fall into the non-fast scrollable region except that we've moved the layer down by 25 pixels. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(40, 10), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), IntSize(0, 1)); + m_hostImpl->scrollEnd(); + + // This point is still inside the non-fast region. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(10, 10), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread); +} + +TEST_F(CCLayerTreeHostImplTest, pinchGesture) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50), IntSize(50, 50)); + initializeRendererAndDrawFrame(); + + CCLayerImpl* scrollLayer = m_hostImpl->rootScrollLayer(); + ASSERT(scrollLayer); + + const float minPageScale = 0.5, maxPageScale = 4; + + // Basic pinch zoom in gesture + { + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + scrollLayer->setPageScaleDelta(1); + scrollLayer->setScrollDelta(IntSize()); + + float pageScaleDelta = 2; + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(pageScaleDelta, IntPoint(50, 50)); + m_hostImpl->pinchGestureEnd(); + EXPECT_TRUE(m_didRequestRedraw); + EXPECT_TRUE(m_didRequestCommit); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, pageScaleDelta); + } + + // Zoom-in clamping + { + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + scrollLayer->setPageScaleDelta(1); + scrollLayer->setScrollDelta(IntSize()); + float pageScaleDelta = 10; + + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(pageScaleDelta, IntPoint(50, 50)); + m_hostImpl->pinchGestureEnd(); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, maxPageScale); + } + + // Zoom-out clamping + { + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + scrollLayer->setPageScaleDelta(1); + scrollLayer->setScrollDelta(IntSize()); + scrollLayer->setScrollPosition(IntPoint(50, 50)); + + float pageScaleDelta = 0.1f; + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(pageScaleDelta, IntPoint(0, 0)); + m_hostImpl->pinchGestureEnd(); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, minPageScale); + + // Pushed to (0,0) via clamping against contents layer size. + expectContains(*scrollInfo, scrollLayer->id(), IntSize(-50, -50)); + } + + // Two-finger panning + { + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + scrollLayer->setPageScaleDelta(1); + scrollLayer->setScrollDelta(IntSize()); + scrollLayer->setScrollPosition(IntPoint(20, 20)); + + float pageScaleDelta = 1; + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(pageScaleDelta, IntPoint(10, 10)); + m_hostImpl->pinchGestureUpdate(pageScaleDelta, IntPoint(20, 20)); + m_hostImpl->pinchGestureEnd(); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, pageScaleDelta); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(-10, -10)); + } +} + +TEST_F(CCLayerTreeHostImplTest, pageScaleAnimation) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50), IntSize(50, 50)); + initializeRendererAndDrawFrame(); + + CCLayerImpl* scrollLayer = m_hostImpl->rootScrollLayer(); + ASSERT(scrollLayer); + + const float minPageScale = 0.5, maxPageScale = 4; + const double startTime = 1; + const double duration = 0.1; + const double halfwayThroughAnimation = startTime + duration / 2; + const double endTime = startTime + duration; + + // Non-anchor zoom-in + { + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + scrollLayer->setPageScaleDelta(1); + scrollLayer->setScrollPosition(IntPoint(50, 50)); + + m_hostImpl->startPageScaleAnimation(IntSize(0, 0), false, 2, startTime, duration); + m_hostImpl->animate(halfwayThroughAnimation, halfwayThroughAnimation); + EXPECT_TRUE(m_didRequestRedraw); + m_hostImpl->animate(endTime, endTime); + EXPECT_TRUE(m_didRequestCommit); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, 2); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(-50, -50)); + } + + // Anchor zoom-out + { + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + scrollLayer->setPageScaleDelta(1); + scrollLayer->setScrollPosition(IntPoint(50, 50)); + + m_hostImpl->startPageScaleAnimation(IntSize(25, 25), true, minPageScale, startTime, duration); + m_hostImpl->animate(endTime, endTime); + EXPECT_TRUE(m_didRequestRedraw); + EXPECT_TRUE(m_didRequestCommit); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, minPageScale); + // Pushed to (0,0) via clamping against contents layer size. + expectContains(*scrollInfo, scrollLayer->id(), IntSize(-50, -50)); + } +} + +TEST_F(CCLayerTreeHostImplTest, inhibitScrollAndPageScaleUpdatesWhilePinchZooming) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50), IntSize(50, 50)); + initializeRendererAndDrawFrame(); + + CCLayerImpl* scrollLayer = m_hostImpl->rootScrollLayer(); + ASSERT(scrollLayer); + + const float minPageScale = 0.5, maxPageScale = 4; + + // Pinch zoom in. + { + // Start a pinch in gesture at the bottom right corner of the viewport. + const float zoomInDelta = 2; + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(zoomInDelta, IntPoint(50, 50)); + + // Because we are pinch zooming in, we shouldn't get any scroll or page + // scale deltas. + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, 1); + EXPECT_EQ(scrollInfo->scrolls.size(), 0u); + + // Once the gesture ends, we get the final scroll and page scale values. + m_hostImpl->pinchGestureEnd(); + scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, zoomInDelta); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(25, 25)); + } + + // Pinch zoom out. + { + // Start a pinch out gesture at the bottom right corner of the viewport. + const float zoomOutDelta = 0.75; + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(zoomOutDelta, IntPoint(50, 50)); + + // Since we are pinch zooming out, we should get an update to zoom all + // the way out to the minimum page scale. + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, minPageScale); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(0, 0)); + + // Once the gesture ends, we get the final scroll and page scale values. + m_hostImpl->pinchGestureEnd(); + scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, zoomOutDelta); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(8, 8)); + } +} + +TEST_F(CCLayerTreeHostImplTest, inhibitScrollAndPageScaleUpdatesWhileAnimatingPageScale) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50), IntSize(50, 50)); + initializeRendererAndDrawFrame(); + + CCLayerImpl* scrollLayer = m_hostImpl->rootScrollLayer(); + ASSERT(scrollLayer); + + const float minPageScale = 0.5, maxPageScale = 4; + const double startTime = 1; + const double duration = 0.1; + const double halfwayThroughAnimation = startTime + duration / 2; + const double endTime = startTime + duration; + + // Start a page scale animation. + const float pageScaleDelta = 2; + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + m_hostImpl->startPageScaleAnimation(IntSize(50, 50), false, pageScaleDelta, startTime, duration); + + // We should immediately get the final zoom and scroll values for the + // animation. + m_hostImpl->animate(halfwayThroughAnimation, halfwayThroughAnimation); + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, pageScaleDelta); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(25, 25)); + + // Scrolling during the animation is ignored. + const IntSize scrollDelta(0, 10); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(25, 25), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), scrollDelta); + m_hostImpl->scrollEnd(); + + // The final page scale and scroll deltas should match what we got + // earlier. + m_hostImpl->animate(endTime, endTime); + scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, pageScaleDelta); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(25, 25)); +} + +class DidDrawCheckLayer : public CCTiledLayerImpl { +public: + static PassOwnPtr<DidDrawCheckLayer> create(int id) { return adoptPtr(new DidDrawCheckLayer(id)); } + + virtual void didDraw(CCResourceProvider*) OVERRIDE + { + m_didDrawCalled = true; + } + + virtual void willDraw(CCResourceProvider*) OVERRIDE + { + m_willDrawCalled = true; + } + + bool didDrawCalled() const { return m_didDrawCalled; } + bool willDrawCalled() const { return m_willDrawCalled; } + + void clearDidDrawCheck() + { + m_didDrawCalled = false; + m_willDrawCalled = false; + } + +protected: + explicit DidDrawCheckLayer(int id) + : CCTiledLayerImpl(id) + , m_didDrawCalled(false) + , m_willDrawCalled(false) + { + setAnchorPoint(FloatPoint(0, 0)); + setBounds(IntSize(10, 10)); + setContentBounds(IntSize(10, 10)); + setDrawsContent(true); + setSkipsDraw(false); + setVisibleContentRect(IntRect(0, 0, 10, 10)); + + OwnPtr<CCLayerTilingData> tiler = CCLayerTilingData::create(IntSize(100, 100), CCLayerTilingData::HasBorderTexels); + tiler->setBounds(contentBounds()); + setTilingData(*tiler.get()); + } + +private: + bool m_didDrawCalled; + bool m_willDrawCalled; +}; + +TEST_F(CCLayerTreeHostImplTest, didDrawNotCalledOnHiddenLayer) +{ + // The root layer is always drawn, so run this test on a child layer that + // will be masked out by the root layer's bounds. + m_hostImpl->setRootLayer(DidDrawCheckLayer::create(1)); + DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(m_hostImpl->rootLayer()); + root->setMasksToBounds(true); + + root->addChild(DidDrawCheckLayer::create(2)); + DidDrawCheckLayer* layer = static_cast<DidDrawCheckLayer*>(root->children()[0].get()); + // Ensure visibleContentRect for layer is empty + layer->setPosition(FloatPoint(100, 100)); + layer->setBounds(IntSize(10, 10)); + layer->setContentBounds(IntSize(10, 10)); + + CCLayerTreeHostImpl::FrameData frame; + + EXPECT_FALSE(layer->willDrawCalled()); + EXPECT_FALSE(layer->didDrawCalled()); + + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + + EXPECT_FALSE(layer->willDrawCalled()); + EXPECT_FALSE(layer->didDrawCalled()); + + EXPECT_TRUE(layer->visibleContentRect().isEmpty()); + + // Ensure visibleContentRect for layer layer is not empty + layer->setPosition(FloatPoint(0, 0)); + + EXPECT_FALSE(layer->willDrawCalled()); + EXPECT_FALSE(layer->didDrawCalled()); + + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + + EXPECT_TRUE(layer->willDrawCalled()); + EXPECT_TRUE(layer->didDrawCalled()); + + EXPECT_FALSE(layer->visibleContentRect().isEmpty()); +} + +TEST_F(CCLayerTreeHostImplTest, willDrawNotCalledOnOccludedLayer) +{ + IntSize bigSize(1000, 1000); + m_hostImpl->setViewportSize(bigSize, bigSize); + + m_hostImpl->setRootLayer(DidDrawCheckLayer::create(1)); + DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(m_hostImpl->rootLayer()); + + root->addChild(DidDrawCheckLayer::create(2)); + DidDrawCheckLayer* occludedLayer = static_cast<DidDrawCheckLayer*>(root->children()[0].get()); + + root->addChild(DidDrawCheckLayer::create(3)); + DidDrawCheckLayer* topLayer = static_cast<DidDrawCheckLayer*>(root->children()[1].get()); + // This layer covers the occludedLayer above. Make this layer large so it can occlude. + topLayer->setBounds(bigSize); + topLayer->setContentBounds(bigSize); + topLayer->setOpaque(true); + + CCLayerTreeHostImpl::FrameData frame; + + EXPECT_FALSE(occludedLayer->willDrawCalled()); + EXPECT_FALSE(occludedLayer->didDrawCalled()); + EXPECT_FALSE(topLayer->willDrawCalled()); + EXPECT_FALSE(topLayer->didDrawCalled()); + + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + + EXPECT_FALSE(occludedLayer->willDrawCalled()); + EXPECT_FALSE(occludedLayer->didDrawCalled()); + EXPECT_TRUE(topLayer->willDrawCalled()); + EXPECT_TRUE(topLayer->didDrawCalled()); +} + +TEST_F(CCLayerTreeHostImplTest, didDrawCalledOnAllLayers) +{ + m_hostImpl->setRootLayer(DidDrawCheckLayer::create(1)); + DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(m_hostImpl->rootLayer()); + + root->addChild(DidDrawCheckLayer::create(2)); + DidDrawCheckLayer* layer1 = static_cast<DidDrawCheckLayer*>(root->children()[0].get()); + + layer1->addChild(DidDrawCheckLayer::create(3)); + DidDrawCheckLayer* layer2 = static_cast<DidDrawCheckLayer*>(layer1->children()[0].get()); + + layer1->setOpacity(0.3f); + layer1->setPreserves3D(false); + + EXPECT_FALSE(root->didDrawCalled()); + EXPECT_FALSE(layer1->didDrawCalled()); + EXPECT_FALSE(layer2->didDrawCalled()); + + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + + EXPECT_TRUE(root->didDrawCalled()); + EXPECT_TRUE(layer1->didDrawCalled()); + EXPECT_TRUE(layer2->didDrawCalled()); + + EXPECT_NE(root->renderSurface(), layer1->renderSurface()); + EXPECT_TRUE(!!layer1->renderSurface()); +} + +class MissingTextureAnimatingLayer : public DidDrawCheckLayer { +public: + static PassOwnPtr<MissingTextureAnimatingLayer> create(int id, bool tileMissing, bool skipsDraw, bool animating, CCResourceProvider* resourceProvider) { return adoptPtr(new MissingTextureAnimatingLayer(id, tileMissing, skipsDraw, animating, resourceProvider)); } + +private: + explicit MissingTextureAnimatingLayer(int id, bool tileMissing, bool skipsDraw, bool animating, CCResourceProvider* resourceProvider) + : DidDrawCheckLayer(id) + { + OwnPtr<CCLayerTilingData> tilingData = CCLayerTilingData::create(IntSize(10, 10), CCLayerTilingData::NoBorderTexels); + tilingData->setBounds(bounds()); + setTilingData(*tilingData.get()); + setSkipsDraw(skipsDraw); + if (!tileMissing) { + CCResourceProvider::ResourceId resource = resourceProvider->createResource(CCRenderer::ContentPool, IntSize(), GraphicsContext3D::RGBA, CCResourceProvider::TextureUsageAny); + pushTileProperties(0, 0, resource, IntRect()); + } + if (animating) + addAnimatedTransformToLayer(*this, 10, 3, 0); + } +}; + +TEST_F(CCLayerTreeHostImplTest, prepareToDrawFailsWhenAnimationUsesCheckerboard) +{ + // When the texture is not missing, we draw as usual. + m_hostImpl->setRootLayer(DidDrawCheckLayer::create(1)); + DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(m_hostImpl->rootLayer()); + root->addChild(MissingTextureAnimatingLayer::create(2, false, false, true, m_hostImpl->resourceProvider())); + + CCLayerTreeHostImpl::FrameData frame; + + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + + // When a texture is missing and we're not animating, we draw as usual with checkerboarding. + m_hostImpl->setRootLayer(DidDrawCheckLayer::create(1)); + root = static_cast<DidDrawCheckLayer*>(m_hostImpl->rootLayer()); + root->addChild(MissingTextureAnimatingLayer::create(2, true, false, false, m_hostImpl->resourceProvider())); + + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + + // When a texture is missing and we're animating, we don't want to draw anything. + m_hostImpl->setRootLayer(DidDrawCheckLayer::create(1)); + root = static_cast<DidDrawCheckLayer*>(m_hostImpl->rootLayer()); + root->addChild(MissingTextureAnimatingLayer::create(2, true, false, true, m_hostImpl->resourceProvider())); + + EXPECT_FALSE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + + // When the layer skips draw and we're animating, we still draw the frame. + m_hostImpl->setRootLayer(DidDrawCheckLayer::create(1)); + root = static_cast<DidDrawCheckLayer*>(m_hostImpl->rootLayer()); + root->addChild(MissingTextureAnimatingLayer::create(2, false, true, true, m_hostImpl->resourceProvider())); + + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); +} + +TEST_F(CCLayerTreeHostImplTest, scrollRootIgnored) +{ + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + root->setScrollable(false); + m_hostImpl->setRootLayer(root.release()); + initializeRendererAndDrawFrame(); + + // Scroll event is ignored because layer is not scrollable. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollIgnored); + EXPECT_FALSE(m_didRequestRedraw); + EXPECT_FALSE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollNonCompositedRoot) +{ + // Test the configuration where a non-composited root layer is embedded in a + // scrollable outer layer. + IntSize surfaceSize(10, 10); + + OwnPtr<CCLayerImpl> contentLayer = CCLayerImpl::create(1); + contentLayer->setUseLCDText(true); + contentLayer->setDrawsContent(true); + contentLayer->setPosition(FloatPoint(0, 0)); + contentLayer->setAnchorPoint(FloatPoint(0, 0)); + contentLayer->setBounds(surfaceSize); + contentLayer->setContentBounds(IntSize(surfaceSize.width() * 2, surfaceSize.height() * 2)); + + OwnPtr<CCLayerImpl> scrollLayer = CCLayerImpl::create(2); + scrollLayer->setScrollable(true); + scrollLayer->setMaxScrollPosition(surfaceSize); + scrollLayer->setBounds(surfaceSize); + scrollLayer->setContentBounds(surfaceSize); + scrollLayer->setPosition(FloatPoint(0, 0)); + scrollLayer->setAnchorPoint(FloatPoint(0, 0)); + scrollLayer->addChild(contentLayer.release()); + + m_hostImpl->setRootLayer(scrollLayer.release()); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), IntSize(0, 10)); + m_hostImpl->scrollEnd(); + EXPECT_TRUE(m_didRequestRedraw); + EXPECT_TRUE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollChildCallsCommitAndRedraw) +{ + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + root->setBounds(surfaceSize); + root->setContentBounds(surfaceSize); + root->addChild(createScrollableLayer(2, surfaceSize)); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), IntSize(0, 10)); + m_hostImpl->scrollEnd(); + EXPECT_TRUE(m_didRequestRedraw); + EXPECT_TRUE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollMissesChild) +{ + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + root->addChild(createScrollableLayer(2, surfaceSize)); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + + // Scroll event is ignored because the input coordinate is outside the layer boundaries. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(15, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollIgnored); + EXPECT_FALSE(m_didRequestRedraw); + EXPECT_FALSE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollMissesBackfacingChild) +{ + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + OwnPtr<CCLayerImpl> child = createScrollableLayer(2, surfaceSize); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + + WebTransformationMatrix matrix; + matrix.rotate3d(180, 0, 0); + child->setTransform(matrix); + child->setDoubleSided(false); + + root->addChild(child.release()); + m_hostImpl->setRootLayer(root.release()); + initializeRendererAndDrawFrame(); + + // Scroll event is ignored because the scrollable layer is not facing the viewer and there is + // nothing scrollable behind it. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollIgnored); + EXPECT_FALSE(m_didRequestRedraw); + EXPECT_FALSE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollBlockedByContentLayer) +{ + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> contentLayer = createScrollableLayer(1, surfaceSize); + contentLayer->setShouldScrollOnMainThread(true); + contentLayer->setScrollable(false); + + OwnPtr<CCLayerImpl> scrollLayer = createScrollableLayer(2, surfaceSize); + scrollLayer->addChild(contentLayer.release()); + + m_hostImpl->setRootLayer(scrollLayer.release()); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + + // Scrolling fails because the content layer is asking to be scrolled on the main thread. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread); +} + +TEST_F(CCLayerTreeHostImplTest, scrollRootAndChangePageScaleOnMainThread) +{ + IntSize surfaceSize(10, 10); + float pageScale = 2; + OwnPtr<CCLayerImpl> root = createScrollableLayer(1, surfaceSize); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + + IntSize scrollDelta(0, 10); + IntSize expectedScrollDelta(scrollDelta); + IntSize expectedMaxScroll(m_hostImpl->rootLayer()->maxScrollPosition()); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), scrollDelta); + m_hostImpl->scrollEnd(); + + // Set new page scale from main thread. + m_hostImpl->setPageScaleFactorAndLimits(pageScale, pageScale, pageScale); + + // The scale should apply to the scroll delta. + expectedScrollDelta.scale(pageScale); + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), expectedScrollDelta); + + // The scroll range should also have been updated. + EXPECT_EQ(m_hostImpl->rootLayer()->maxScrollPosition(), expectedMaxScroll); + + // The page scale delta remains constant because the impl thread did not scale. + EXPECT_EQ(m_hostImpl->rootLayer()->pageScaleDelta(), 1); +} + +TEST_F(CCLayerTreeHostImplTest, scrollRootAndChangePageScaleOnImplThread) +{ + IntSize surfaceSize(10, 10); + float pageScale = 2; + OwnPtr<CCLayerImpl> root = createScrollableLayer(1, surfaceSize); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + m_hostImpl->setPageScaleFactorAndLimits(1, 1, pageScale); + initializeRendererAndDrawFrame(); + + IntSize scrollDelta(0, 10); + IntSize expectedScrollDelta(scrollDelta); + IntSize expectedMaxScroll(m_hostImpl->rootLayer()->maxScrollPosition()); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), scrollDelta); + m_hostImpl->scrollEnd(); + + // Set new page scale on impl thread by pinching. + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(pageScale, IntPoint()); + m_hostImpl->pinchGestureEnd(); + + // The scroll delta is not scaled because the main thread did not scale. + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), expectedScrollDelta); + + // The scroll range should also have been updated. + EXPECT_EQ(m_hostImpl->rootLayer()->maxScrollPosition(), expectedMaxScroll); + + // The page scale delta should match the new scale on the impl side. + EXPECT_EQ(m_hostImpl->rootLayer()->pageScaleDelta(), pageScale); +} + +TEST_F(CCLayerTreeHostImplTest, pageScaleDeltaAppliedToRootScrollLayerOnly) +{ + IntSize surfaceSize(10, 10); + float defaultPageScale = 1; + float newPageScale = 2; + + // Create a normal scrollable root layer and another scrollable child layer. + setupScrollAndContentsLayers(surfaceSize); + CCLayerImpl* root = m_hostImpl->rootLayer(); + CCLayerImpl* child = root->children()[0].get(); + + OwnPtr<CCLayerImpl> scrollableChild = createScrollableLayer(3, surfaceSize); + child->addChild(scrollableChild.release()); + CCLayerImpl* grandChild = child->children()[0].get(); + + // Set new page scale on impl thread by pinching. + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(newPageScale, IntPoint()); + m_hostImpl->pinchGestureEnd(); + + // The page scale delta should only be applied to the scrollable root layer. + EXPECT_EQ(root->pageScaleDelta(), newPageScale); + EXPECT_EQ(child->pageScaleDelta(), defaultPageScale); + EXPECT_EQ(grandChild->pageScaleDelta(), defaultPageScale); + + // Make sure all the layers are drawn with the page scale delta applied, i.e., the page scale + // delta on the root layer is applied hierarchically. + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + + EXPECT_EQ(root->drawTransform().m11(), newPageScale); + EXPECT_EQ(root->drawTransform().m22(), newPageScale); + EXPECT_EQ(child->drawTransform().m11(), newPageScale); + EXPECT_EQ(child->drawTransform().m22(), newPageScale); + EXPECT_EQ(grandChild->drawTransform().m11(), newPageScale); + EXPECT_EQ(grandChild->drawTransform().m22(), newPageScale); +} + +TEST_F(CCLayerTreeHostImplTest, scrollChildAndChangePageScaleOnMainThread) +{ + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + root->setBounds(surfaceSize); + root->setContentBounds(surfaceSize); + // Also mark the root scrollable so it becomes the root scroll layer. + root->setScrollable(true); + int scrollLayerId = 2; + root->addChild(createScrollableLayer(scrollLayerId, surfaceSize)); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + + CCLayerImpl* child = m_hostImpl->rootLayer()->children()[0].get(); + + IntSize scrollDelta(0, 10); + IntSize expectedScrollDelta(scrollDelta); + IntSize expectedMaxScroll(child->maxScrollPosition()); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), scrollDelta); + m_hostImpl->scrollEnd(); + + float pageScale = 2; + m_hostImpl->setPageScaleFactorAndLimits(pageScale, 1, pageScale); + + // The scale should apply to the scroll delta. + expectedScrollDelta.scale(pageScale); + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), scrollLayerId, expectedScrollDelta); + + // The scroll range should not have changed. + EXPECT_EQ(child->maxScrollPosition(), expectedMaxScroll); + + // The page scale delta remains constant because the impl thread did not scale. + EXPECT_EQ(child->pageScaleDelta(), 1); +} + +TEST_F(CCLayerTreeHostImplTest, scrollChildBeyondLimit) +{ + // Scroll a child layer beyond its maximum scroll range and make sure the + // parent layer is scrolled on the axis on which the child was unable to + // scroll. + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = createScrollableLayer(1, surfaceSize); + + OwnPtr<CCLayerImpl> grandChild = createScrollableLayer(3, surfaceSize); + grandChild->setScrollPosition(IntPoint(0, 5)); + + OwnPtr<CCLayerImpl> child = createScrollableLayer(2, surfaceSize); + child->setScrollPosition(IntPoint(3, 0)); + child->addChild(grandChild.release()); + + root->addChild(child.release()); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + { + IntSize scrollDelta(-8, -7); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), scrollDelta); + m_hostImpl->scrollEnd(); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + + // The grand child should have scrolled up to its limit. + CCLayerImpl* child = m_hostImpl->rootLayer()->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + expectContains(*scrollInfo.get(), grandChild->id(), IntSize(0, -5)); + + // The child should have only scrolled on the other axis. + expectContains(*scrollInfo.get(), child->id(), IntSize(-3, 0)); + } +} + +TEST_F(CCLayerTreeHostImplTest, scrollEventBubbling) +{ + // When we try to scroll a non-scrollable child layer, the scroll delta + // should be applied to one of its ancestors if possible. + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = createScrollableLayer(1, surfaceSize); + OwnPtr<CCLayerImpl> child = createScrollableLayer(2, surfaceSize); + + child->setScrollable(false); + root->addChild(child.release()); + + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + { + IntSize scrollDelta(0, 4); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), scrollDelta); + m_hostImpl->scrollEnd(); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + + // Only the root should have scrolled. + ASSERT_EQ(scrollInfo->scrolls.size(), 1u); + expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), scrollDelta); + } +} + +TEST_F(CCLayerTreeHostImplTest, scrollBeforeRedraw) +{ + IntSize surfaceSize(10, 10); + m_hostImpl->setRootLayer(createScrollableLayer(1, surfaceSize)); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + + // Draw one frame and then immediately rebuild the layer tree to mimic a tree synchronization. + initializeRendererAndDrawFrame(); + m_hostImpl->detachLayerTree(); + m_hostImpl->setRootLayer(createScrollableLayer(2, surfaceSize)); + + // Scrolling should still work even though we did not draw yet. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); +} + +TEST_F(CCLayerTreeHostImplTest, scrollAxisAlignedRotatedLayer) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + + // Rotate the root layer 90 degrees counter-clockwise about its center. + WebTransformationMatrix rotateTransform; + rotateTransform.rotate(-90); + m_hostImpl->rootLayer()->setTransform(rotateTransform); + + IntSize surfaceSize(50, 50); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + + // Scroll to the right in screen coordinates with a gesture. + IntSize gestureScrollDelta(10, 0); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), gestureScrollDelta); + m_hostImpl->scrollEnd(); + + // The layer should have scrolled down in its local coordinates. + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), IntSize(0, gestureScrollDelta.width())); + + // Reset and scroll down with the wheel. + m_hostImpl->rootLayer()->setScrollDelta(FloatSize()); + IntSize wheelScrollDelta(0, 10); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), wheelScrollDelta); + m_hostImpl->scrollEnd(); + + // The layer should have scrolled down in its local coordinates. + scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), wheelScrollDelta); +} + +TEST_F(CCLayerTreeHostImplTest, scrollNonAxisAlignedRotatedLayer) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + int childLayerId = 3; + float childLayerAngle = -20; + + // Create a child layer that is rotated to a non-axis-aligned angle. + OwnPtr<CCLayerImpl> child = createScrollableLayer(childLayerId, m_hostImpl->rootLayer()->contentBounds()); + WebTransformationMatrix rotateTransform; + rotateTransform.translate(-50, -50); + rotateTransform.rotate(childLayerAngle); + rotateTransform.translate(50, 50); + child->setTransform(rotateTransform); + + // Only allow vertical scrolling. + child->setMaxScrollPosition(IntSize(0, child->contentBounds().height())); + m_hostImpl->rootLayer()->addChild(child.release()); + + IntSize surfaceSize(50, 50); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + + { + // Scroll down in screen coordinates with a gesture. + IntSize gestureScrollDelta(0, 10); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), gestureScrollDelta); + m_hostImpl->scrollEnd(); + + // The child layer should have scrolled down in its local coordinates an amount proportional to + // the angle between it and the input scroll delta. + IntSize expectedScrollDelta(0, gestureScrollDelta.height() * cosf(deg2rad(childLayerAngle))); + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), childLayerId, expectedScrollDelta); + + // The root layer should not have scrolled, because the input delta was close to the layer's + // axis of movement. + EXPECT_EQ(scrollInfo->scrolls.size(), 1u); + } + + { + // Now reset and scroll the same amount horizontally. + m_hostImpl->rootLayer()->children()[1]->setScrollDelta(FloatSize()); + IntSize gestureScrollDelta(10, 0); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), gestureScrollDelta); + m_hostImpl->scrollEnd(); + + // The child layer should have scrolled down in its local coordinates an amount proportional to + // the angle between it and the input scroll delta. + IntSize expectedScrollDelta(0, -gestureScrollDelta.width() * sinf(deg2rad(childLayerAngle))); + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), childLayerId, expectedScrollDelta); + + // The root layer should have scrolled more, since the input scroll delta was mostly + // orthogonal to the child layer's vertical scroll axis. + IntSize expectedRootScrollDelta(gestureScrollDelta.width() * pow(cosf(deg2rad(childLayerAngle)), 2), 0); + expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), expectedRootScrollDelta); + } +} + +TEST_F(CCLayerTreeHostImplTest, scrollScaledLayer) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + + // Scale the layer to twice its normal size. + int scale = 2; + WebTransformationMatrix scaleTransform; + scaleTransform.scale(scale); + m_hostImpl->rootLayer()->setTransform(scaleTransform); + + IntSize surfaceSize(50, 50); + m_hostImpl->setViewportSize(surfaceSize, surfaceSize); + initializeRendererAndDrawFrame(); + + // Scroll down in screen coordinates with a gesture. + IntSize scrollDelta(0, 10); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), scrollDelta); + m_hostImpl->scrollEnd(); + + // The layer should have scrolled down in its local coordinates, but half he amount. + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), IntSize(0, scrollDelta.height() / scale)); + + // Reset and scroll down with the wheel. + m_hostImpl->rootLayer()->setScrollDelta(FloatSize()); + IntSize wheelScrollDelta(0, 10); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntPoint(), wheelScrollDelta); + m_hostImpl->scrollEnd(); + + // The scale should not have been applied to the scroll delta. + scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), wheelScrollDelta); +} + +class BlendStateTrackerContext: public FakeWebGraphicsContext3D { +public: + BlendStateTrackerContext() : m_blend(false) { } + + virtual void enable(WGC3Denum cap) + { + if (cap == GraphicsContext3D::BLEND) + m_blend = true; + } + + virtual void disable(WGC3Denum cap) + { + if (cap == GraphicsContext3D::BLEND) + m_blend = false; + } + + bool blend() const { return m_blend; } + +private: + bool m_blend; +}; + +class BlendStateCheckLayer : public CCLayerImpl { +public: + static PassOwnPtr<BlendStateCheckLayer> create(int id, CCResourceProvider* resourceProvider) { return adoptPtr(new BlendStateCheckLayer(id, resourceProvider)); } + + virtual void appendQuads(CCQuadSink& quadSink, bool&) OVERRIDE + { + m_quadsAppended = true; + + IntRect opaqueRect; + if (opaque() || m_opaqueContents) + opaqueRect = m_quadRect; + else + opaqueRect = m_opaqueContentRect; + + CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState()); + OwnPtr<CCDrawQuad> testBlendingDrawQuad = CCTileDrawQuad::create(sharedQuadState, m_quadRect, opaqueRect, m_resourceId, IntPoint(), IntSize(1, 1), 0, false, false, false, false, false); + testBlendingDrawQuad->setQuadVisibleRect(m_quadVisibleRect); + EXPECT_EQ(m_blend, testBlendingDrawQuad->needsBlending()); + EXPECT_EQ(m_hasRenderSurface, !!renderSurface()); + quadSink.append(testBlendingDrawQuad.release()); + } + + void setExpectation(bool blend, bool hasRenderSurface) + { + m_blend = blend; + m_hasRenderSurface = hasRenderSurface; + m_quadsAppended = false; + } + + bool quadsAppended() const { return m_quadsAppended; } + + void setQuadRect(const IntRect& rect) { m_quadRect = rect; } + void setQuadVisibleRect(const IntRect& rect) { m_quadVisibleRect = rect; } + void setOpaqueContents(bool opaque) { m_opaqueContents = opaque; } + void setOpaqueContentRect(const IntRect& rect) { m_opaqueContentRect = rect; } + +private: + explicit BlendStateCheckLayer(int id, CCResourceProvider* resourceProvider) + : CCLayerImpl(id) + , m_blend(false) + , m_hasRenderSurface(false) + , m_quadsAppended(false) + , m_opaqueContents(false) + , m_quadRect(5, 5, 5, 5) + , m_quadVisibleRect(5, 5, 5, 5) + , m_resourceId(resourceProvider->createResource(CCRenderer::ContentPool, IntSize(1, 1), GraphicsContext3D::RGBA, CCResourceProvider::TextureUsageAny)) + { + setAnchorPoint(FloatPoint(0, 0)); + setBounds(IntSize(10, 10)); + setContentBounds(IntSize(10, 10)); + setDrawsContent(true); + } + + bool m_blend; + bool m_hasRenderSurface; + bool m_quadsAppended; + bool m_opaqueContents; + IntRect m_quadRect; + IntRect m_opaqueContentRect; + IntRect m_quadVisibleRect; + CCResourceProvider::ResourceId m_resourceId; +}; + +TEST_F(CCLayerTreeHostImplTest, blendingOffWhenDrawingOpaqueLayers) +{ + { + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + root->setAnchorPoint(FloatPoint(0, 0)); + root->setBounds(IntSize(10, 10)); + root->setContentBounds(root->bounds()); + root->setDrawsContent(false); + m_hostImpl->setRootLayer(root.release()); + } + CCLayerImpl* root = m_hostImpl->rootLayer(); + + root->addChild(BlendStateCheckLayer::create(2, m_hostImpl->resourceProvider())); + BlendStateCheckLayer* layer1 = static_cast<BlendStateCheckLayer*>(root->children()[0].get()); + layer1->setPosition(FloatPoint(2, 2)); + + CCLayerTreeHostImpl::FrameData frame; + + // Opaque layer, drawn without blending. + layer1->setOpaque(true); + layer1->setOpaqueContents(true); + layer1->setExpectation(false, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Layer with translucent content, but opaque content, so drawn without blending. + layer1->setOpaque(false); + layer1->setOpaqueContents(true); + layer1->setExpectation(false, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Layer with translucent content and painting, so drawn with blending. + layer1->setOpaque(false); + layer1->setOpaqueContents(false); + layer1->setExpectation(true, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Layer with translucent opacity, drawn with blending. + layer1->setOpaque(true); + layer1->setOpaqueContents(true); + layer1->setOpacity(0.5); + layer1->setExpectation(true, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Layer with translucent opacity and painting, drawn with blending. + layer1->setOpaque(true); + layer1->setOpaqueContents(false); + layer1->setOpacity(0.5); + layer1->setExpectation(true, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + layer1->addChild(BlendStateCheckLayer::create(3, m_hostImpl->resourceProvider())); + BlendStateCheckLayer* layer2 = static_cast<BlendStateCheckLayer*>(layer1->children()[0].get()); + layer2->setPosition(FloatPoint(4, 4)); + + // 2 opaque layers, drawn without blending. + layer1->setOpaque(true); + layer1->setOpaqueContents(true); + layer1->setOpacity(1); + layer1->setExpectation(false, false); + layer2->setOpaque(true); + layer2->setOpaqueContents(true); + layer2->setOpacity(1); + layer2->setExpectation(false, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + EXPECT_TRUE(layer2->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Parent layer with translucent content, drawn with blending. + // Child layer with opaque content, drawn without blending. + layer1->setOpaque(false); + layer1->setOpaqueContents(false); + layer1->setExpectation(true, false); + layer2->setExpectation(false, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + EXPECT_TRUE(layer2->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Parent layer with translucent content but opaque painting, drawn without blending. + // Child layer with opaque content, drawn without blending. + layer1->setOpaque(false); + layer1->setOpaqueContents(true); + layer1->setExpectation(false, false); + layer2->setExpectation(false, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + EXPECT_TRUE(layer2->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Parent layer with translucent opacity and opaque content. Since it has a + // drawing child, it's drawn to a render surface which carries the opacity, + // so it's itself drawn without blending. + // Child layer with opaque content, drawn without blending (parent surface + // carries the inherited opacity). + layer1->setOpaque(true); + layer1->setOpaqueContents(true); + layer1->setOpacity(0.5); + layer1->setExpectation(false, true); + layer2->setExpectation(false, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + EXPECT_TRUE(layer2->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Draw again, but with child non-opaque, to make sure + // layer1 not culled. + layer1->setOpaque(true); + layer1->setOpaqueContents(true); + layer1->setOpacity(1); + layer1->setExpectation(false, false); + layer2->setOpaque(true); + layer2->setOpaqueContents(true); + layer2->setOpacity(0.5); + layer2->setExpectation(true, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + EXPECT_TRUE(layer2->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // A second way of making the child non-opaque. + layer1->setOpaque(true); + layer1->setOpacity(1); + layer1->setExpectation(false, false); + layer2->setOpaque(false); + layer2->setOpaqueContents(false); + layer2->setOpacity(1); + layer2->setExpectation(true, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + EXPECT_TRUE(layer2->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // And when the layer says its not opaque but is painted opaque, it is not blended. + layer1->setOpaque(true); + layer1->setOpacity(1); + layer1->setExpectation(false, false); + layer2->setOpaque(false); + layer2->setOpaqueContents(true); + layer2->setOpacity(1); + layer2->setExpectation(false, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + EXPECT_TRUE(layer2->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Layer with partially opaque contents, drawn with blending. + layer1->setOpaque(false); + layer1->setQuadRect(IntRect(5, 5, 5, 5)); + layer1->setQuadVisibleRect(IntRect(5, 5, 5, 5)); + layer1->setOpaqueContents(false); + layer1->setOpaqueContentRect(IntRect(5, 5, 2, 5)); + layer1->setExpectation(true, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Layer with partially opaque contents partially culled, drawn with blending. + layer1->setOpaque(false); + layer1->setQuadRect(IntRect(5, 5, 5, 5)); + layer1->setQuadVisibleRect(IntRect(5, 5, 5, 2)); + layer1->setOpaqueContents(false); + layer1->setOpaqueContentRect(IntRect(5, 5, 2, 5)); + layer1->setExpectation(true, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Layer with partially opaque contents culled, drawn with blending. + layer1->setOpaque(false); + layer1->setQuadRect(IntRect(5, 5, 5, 5)); + layer1->setQuadVisibleRect(IntRect(7, 5, 3, 5)); + layer1->setOpaqueContents(false); + layer1->setOpaqueContentRect(IntRect(5, 5, 2, 5)); + layer1->setExpectation(true, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + + // Layer with partially opaque contents and translucent contents culled, drawn without blending. + layer1->setOpaque(false); + layer1->setQuadRect(IntRect(5, 5, 5, 5)); + layer1->setQuadVisibleRect(IntRect(5, 5, 2, 5)); + layer1->setOpaqueContents(false); + layer1->setOpaqueContentRect(IntRect(5, 5, 2, 5)); + layer1->setExpectation(false, false); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(layer1->quadsAppended()); + m_hostImpl->didDrawAllLayers(frame); + +} + +TEST_F(CCLayerTreeHostImplTest, viewportCovered) +{ + m_hostImpl->initializeRenderer(createContext(), UnthrottledUploader); + m_hostImpl->setBackgroundColor(SK_ColorGRAY); + + IntSize viewportSize(1000, 1000); + m_hostImpl->setViewportSize(viewportSize, viewportSize); + + m_hostImpl->setRootLayer(BlendStateCheckLayer::create(1, m_hostImpl->resourceProvider())); + BlendStateCheckLayer* root = static_cast<BlendStateCheckLayer*>(m_hostImpl->rootLayer()); + root->setExpectation(false, true); + root->setOpaque(true); + + // No gutter rects + { + IntRect layerRect(0, 0, 1000, 1000); + root->setPosition(layerRect.location()); + root->setBounds(layerRect.size()); + root->setContentBounds(layerRect.size()); + root->setQuadRect(IntRect(IntPoint(), layerRect.size())); + root->setQuadVisibleRect(IntRect(IntPoint(), layerRect.size())); + + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + ASSERT_EQ(1u, frame.renderPasses.size()); + + size_t numGutterQuads = 0; + for (size_t i = 0; i < frame.renderPasses[0]->quadList().size(); ++i) + numGutterQuads += (frame.renderPasses[0]->quadList()[i]->material() == CCDrawQuad::SolidColor) ? 1 : 0; + EXPECT_EQ(0u, numGutterQuads); + EXPECT_EQ(1u, frame.renderPasses[0]->quadList().size()); + + verifyQuadsExactlyCoverRect(frame.renderPasses[0]->quadList(), IntRect(-layerRect.location(), viewportSize)); + m_hostImpl->didDrawAllLayers(frame); + } + + // Empty visible content area (fullscreen gutter rect) + { + IntRect layerRect(0, 0, 0, 0); + root->setPosition(layerRect.location()); + root->setBounds(layerRect.size()); + root->setContentBounds(layerRect.size()); + root->setQuadRect(IntRect(IntPoint(), layerRect.size())); + root->setQuadVisibleRect(IntRect(IntPoint(), layerRect.size())); + + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + ASSERT_EQ(1u, frame.renderPasses.size()); + m_hostImpl->didDrawAllLayers(frame); + + size_t numGutterQuads = 0; + for (size_t i = 0; i < frame.renderPasses[0]->quadList().size(); ++i) + numGutterQuads += (frame.renderPasses[0]->quadList()[i]->material() == CCDrawQuad::SolidColor) ? 1 : 0; + EXPECT_EQ(1u, numGutterQuads); + EXPECT_EQ(1u, frame.renderPasses[0]->quadList().size()); + + verifyQuadsExactlyCoverRect(frame.renderPasses[0]->quadList(), IntRect(-layerRect.location(), viewportSize)); + m_hostImpl->didDrawAllLayers(frame); + } + + // Content area in middle of clip rect (four surrounding gutter rects) + { + IntRect layerRect(500, 500, 200, 200); + root->setPosition(layerRect.location()); + root->setBounds(layerRect.size()); + root->setContentBounds(layerRect.size()); + root->setQuadRect(IntRect(IntPoint(), layerRect.size())); + root->setQuadVisibleRect(IntRect(IntPoint(), layerRect.size())); + + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + ASSERT_EQ(1u, frame.renderPasses.size()); + + size_t numGutterQuads = 0; + for (size_t i = 0; i < frame.renderPasses[0]->quadList().size(); ++i) + numGutterQuads += (frame.renderPasses[0]->quadList()[i]->material() == CCDrawQuad::SolidColor) ? 1 : 0; + EXPECT_EQ(4u, numGutterQuads); + EXPECT_EQ(5u, frame.renderPasses[0]->quadList().size()); + + verifyQuadsExactlyCoverRect(frame.renderPasses[0]->quadList(), IntRect(-layerRect.location(), viewportSize)); + m_hostImpl->didDrawAllLayers(frame); + } + +} + + +class ReshapeTrackerContext: public FakeWebGraphicsContext3D { +public: + ReshapeTrackerContext() : m_reshapeCalled(false) { } + + virtual void reshape(int width, int height) + { + m_reshapeCalled = true; + } + + bool reshapeCalled() const { return m_reshapeCalled; } + +private: + bool m_reshapeCalled; +}; + +class FakeDrawableCCLayerImpl: public CCLayerImpl { +public: + explicit FakeDrawableCCLayerImpl(int id) : CCLayerImpl(id) { } +}; + +// 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. +TEST_F(CCLayerTreeHostImplTest, reshapeNotCalledUntilDraw) +{ + OwnPtr<CCGraphicsContext> ccContext = FakeWebCompositorOutputSurface::create(adoptPtr(new ReshapeTrackerContext)); + ReshapeTrackerContext* reshapeTracker = static_cast<ReshapeTrackerContext*>(ccContext->context3D()); + m_hostImpl->initializeRenderer(ccContext.release(), UnthrottledUploader); + + CCLayerImpl* root = new FakeDrawableCCLayerImpl(1); + root->setAnchorPoint(FloatPoint(0, 0)); + root->setBounds(IntSize(10, 10)); + root->setDrawsContent(true); + m_hostImpl->setRootLayer(adoptPtr(root)); + EXPECT_FALSE(reshapeTracker->reshapeCalled()); + + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + EXPECT_TRUE(reshapeTracker->reshapeCalled()); + m_hostImpl->didDrawAllLayers(frame); +} + +class PartialSwapTrackerContext : public FakeWebGraphicsContext3D { +public: + virtual void postSubBufferCHROMIUM(int x, int y, int width, int height) + { + m_partialSwapRect = IntRect(x, y, width, height); + } + + virtual WebString getString(WGC3Denum name) + { + if (name == GraphicsContext3D::EXTENSIONS) + return WebString("GL_CHROMIUM_post_sub_buffer GL_CHROMIUM_set_visibility"); + + return WebString(); + } + + IntRect partialSwapRect() const { return m_partialSwapRect; } + +private: + IntRect m_partialSwapRect; +}; + +// Make sure damage tracking propagates all the way to the graphics context, +// where it should request to swap only the subBuffer that is damaged. +TEST_F(CCLayerTreeHostImplTest, partialSwapReceivesDamageRect) +{ + OwnPtr<CCGraphicsContext> ccContext = FakeWebCompositorOutputSurface::create(adoptPtr(new PartialSwapTrackerContext)); + PartialSwapTrackerContext* partialSwapTracker = static_cast<PartialSwapTrackerContext*>(ccContext->context3D()); + + // This test creates its own CCLayerTreeHostImpl, so + // that we can force partial swap enabled. + CCLayerTreeSettings settings; + CCSettings::setPartialSwapEnabled(true); + OwnPtr<CCLayerTreeHostImpl> layerTreeHostImpl = CCLayerTreeHostImpl::create(settings, this); + layerTreeHostImpl->initializeRenderer(ccContext.release(), UnthrottledUploader); + layerTreeHostImpl->setViewportSize(IntSize(500, 500), IntSize(500, 500)); + + CCLayerImpl* root = new FakeDrawableCCLayerImpl(1); + CCLayerImpl* child = new FakeDrawableCCLayerImpl(2); + child->setPosition(FloatPoint(12, 13)); + child->setAnchorPoint(FloatPoint(0, 0)); + child->setBounds(IntSize(14, 15)); + child->setContentBounds(IntSize(14, 15)); + child->setDrawsContent(true); + root->setAnchorPoint(FloatPoint(0, 0)); + root->setBounds(IntSize(500, 500)); + root->setContentBounds(IntSize(500, 500)); + root->setDrawsContent(true); + root->addChild(adoptPtr(child)); + layerTreeHostImpl->setRootLayer(adoptPtr(root)); + + CCLayerTreeHostImpl::FrameData frame; + + // First frame, the entire screen should get swapped. + EXPECT_TRUE(layerTreeHostImpl->prepareToDraw(frame)); + layerTreeHostImpl->drawLayers(frame); + layerTreeHostImpl->didDrawAllLayers(frame); + layerTreeHostImpl->swapBuffers(); + IntRect actualSwapRect = partialSwapTracker->partialSwapRect(); + IntRect expectedSwapRect = IntRect(IntPoint::zero(), IntSize(500, 500)); + EXPECT_EQ(expectedSwapRect.x(), actualSwapRect.x()); + EXPECT_EQ(expectedSwapRect.y(), actualSwapRect.y()); + EXPECT_EQ(expectedSwapRect.width(), actualSwapRect.width()); + EXPECT_EQ(expectedSwapRect.height(), actualSwapRect.height()); + + // Second frame, only the damaged area should get swapped. Damage should be the union + // of old and new child rects. + // expected damage rect: IntRect(IntPoint::zero(), IntSize(26, 28)); + // expected swap rect: vertically flipped, with origin at bottom left corner. + child->setPosition(FloatPoint(0, 0)); + EXPECT_TRUE(layerTreeHostImpl->prepareToDraw(frame)); + layerTreeHostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + layerTreeHostImpl->swapBuffers(); + actualSwapRect = partialSwapTracker->partialSwapRect(); + expectedSwapRect = IntRect(IntPoint(0, 500-28), IntSize(26, 28)); + EXPECT_EQ(expectedSwapRect.x(), actualSwapRect.x()); + EXPECT_EQ(expectedSwapRect.y(), actualSwapRect.y()); + EXPECT_EQ(expectedSwapRect.width(), actualSwapRect.width()); + EXPECT_EQ(expectedSwapRect.height(), actualSwapRect.height()); + + // Make sure that partial swap is constrained to the viewport dimensions + // expected damage rect: IntRect(IntPoint::zero(), IntSize(500, 500)); + // expected swap rect: flipped damage rect, but also clamped to viewport + layerTreeHostImpl->setViewportSize(IntSize(10, 10), IntSize(10, 10)); + root->setOpacity(0.7f); // this will damage everything + EXPECT_TRUE(layerTreeHostImpl->prepareToDraw(frame)); + layerTreeHostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + layerTreeHostImpl->swapBuffers(); + actualSwapRect = partialSwapTracker->partialSwapRect(); + expectedSwapRect = IntRect(IntPoint::zero(), IntSize(10, 10)); + EXPECT_EQ(expectedSwapRect.x(), actualSwapRect.x()); + EXPECT_EQ(expectedSwapRect.y(), actualSwapRect.y()); + EXPECT_EQ(expectedSwapRect.width(), actualSwapRect.width()); + EXPECT_EQ(expectedSwapRect.height(), actualSwapRect.height()); +} + +TEST_F(CCLayerTreeHostImplTest, rootLayerDoesntCreateExtraSurface) +{ + CCLayerImpl* root = new FakeDrawableCCLayerImpl(1); + CCLayerImpl* child = new FakeDrawableCCLayerImpl(2); + child->setAnchorPoint(FloatPoint(0, 0)); + child->setBounds(IntSize(10, 10)); + child->setContentBounds(IntSize(10, 10)); + child->setDrawsContent(true); + root->setAnchorPoint(FloatPoint(0, 0)); + root->setBounds(IntSize(10, 10)); + root->setContentBounds(IntSize(10, 10)); + root->setDrawsContent(true); + root->setOpacity(0.7f); + root->addChild(adoptPtr(child)); + + m_hostImpl->setRootLayer(adoptPtr(root)); + + CCLayerTreeHostImpl::FrameData frame; + + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + EXPECT_EQ(1u, frame.renderSurfaceLayerList->size()); + EXPECT_EQ(1u, frame.renderPasses.size()); + m_hostImpl->didDrawAllLayers(frame); +} + +} // namespace + +class FakeLayerWithQuads : public CCLayerImpl { +public: + static PassOwnPtr<FakeLayerWithQuads> create(int id) { return adoptPtr(new FakeLayerWithQuads(id)); } + + virtual void appendQuads(CCQuadSink& quadSink, bool&) OVERRIDE + { + CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState()); + + SkColor gray = SkColorSetRGB(100, 100, 100); + IntRect quadRect(IntPoint(0, 0), contentBounds()); + OwnPtr<CCDrawQuad> myQuad = CCSolidColorDrawQuad::create(sharedQuadState, quadRect, gray); + quadSink.append(myQuad.release()); + } + +private: + FakeLayerWithQuads(int id) + : CCLayerImpl(id) + { + } +}; + +namespace { + +class MockContext : public FakeWebGraphicsContext3D { +public: + MOCK_METHOD1(useProgram, void(WebGLId program)); + MOCK_METHOD5(uniform4f, void(WGC3Dint location, WGC3Dfloat x, WGC3Dfloat y, WGC3Dfloat z, WGC3Dfloat w)); + MOCK_METHOD4(uniformMatrix4fv, void(WGC3Dint location, WGC3Dsizei count, WGC3Dboolean transpose, const WGC3Dfloat* value)); + MOCK_METHOD4(drawElements, void(WGC3Denum mode, WGC3Dsizei count, WGC3Denum type, WGC3Dintptr offset)); + MOCK_METHOD1(getString, WebString(WGC3Denum name)); + MOCK_METHOD0(getRequestableExtensionsCHROMIUM, WebString()); + MOCK_METHOD1(enable, void(WGC3Denum cap)); + MOCK_METHOD1(disable, void(WGC3Denum cap)); + MOCK_METHOD4(scissor, void(WGC3Dint x, WGC3Dint y, WGC3Dsizei width, WGC3Dsizei height)); +}; + +class MockContextHarness { +private: + MockContext* m_context; +public: + MockContextHarness(MockContext* context) + : m_context(context) + { + // Catch "uninteresting" calls + EXPECT_CALL(*m_context, useProgram(_)) + .Times(0); + + EXPECT_CALL(*m_context, drawElements(_, _, _, _)) + .Times(0); + + // These are not asserted + EXPECT_CALL(*m_context, uniformMatrix4fv(_, _, _, _)) + .WillRepeatedly(Return()); + + EXPECT_CALL(*m_context, uniform4f(_, _, _, _, _)) + .WillRepeatedly(Return()); + + // Any other strings are empty + EXPECT_CALL(*m_context, getString(_)) + .WillRepeatedly(Return(WebString())); + + // Support for partial swap, if needed + EXPECT_CALL(*m_context, getString(GraphicsContext3D::EXTENSIONS)) + .WillRepeatedly(Return(WebString("GL_CHROMIUM_post_sub_buffer"))); + + EXPECT_CALL(*m_context, getRequestableExtensionsCHROMIUM()) + .WillRepeatedly(Return(WebString("GL_CHROMIUM_post_sub_buffer"))); + + // Any un-sanctioned calls to enable() are OK + EXPECT_CALL(*m_context, enable(_)) + .WillRepeatedly(Return()); + + // Any un-sanctioned calls to disable() are OK + EXPECT_CALL(*m_context, disable(_)) + .WillRepeatedly(Return()); + } + + void mustDrawSolidQuad() + { + EXPECT_CALL(*m_context, drawElements(GraphicsContext3D::TRIANGLES, 6, GraphicsContext3D::UNSIGNED_SHORT, 0)) + .WillOnce(Return()) + .RetiresOnSaturation(); + + // 1 is hardcoded return value of fake createProgram() + EXPECT_CALL(*m_context, useProgram(1)) + .WillOnce(Return()) + .RetiresOnSaturation(); + + } + + void mustSetScissor(int x, int y, int width, int height) + { + EXPECT_CALL(*m_context, enable(GraphicsContext3D::SCISSOR_TEST)) + .WillRepeatedly(Return()); + + EXPECT_CALL(*m_context, scissor(x, y, width, height)) + .Times(AtLeast(1)) + .WillRepeatedly(Return()); + } + + void mustSetNoScissor() + { + EXPECT_CALL(*m_context, disable(GraphicsContext3D::SCISSOR_TEST)) + .WillRepeatedly(Return()); + + EXPECT_CALL(*m_context, enable(GraphicsContext3D::SCISSOR_TEST)) + .Times(0); + + EXPECT_CALL(*m_context, scissor(_, _, _, _)) + .Times(0); + } +}; + +TEST_F(CCLayerTreeHostImplTest, noPartialSwap) +{ + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new MockContext)); + MockContext* mockContext = static_cast<MockContext*>(context->context3D()); + MockContextHarness harness(mockContext); + + harness.mustDrawSolidQuad(); + harness.mustSetScissor(0, 0, 10, 10); + + // Run test case + OwnPtr<CCLayerTreeHostImpl> myHostImpl = createLayerTreeHost(false, context.release(), FakeLayerWithQuads::create(1)); + + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + Mock::VerifyAndClearExpectations(&mockContext); +} + +TEST_F(CCLayerTreeHostImplTest, partialSwap) +{ + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new MockContext)); + MockContext* mockContext = static_cast<MockContext*>(context->context3D()); + MockContextHarness harness(mockContext); + + OwnPtr<CCLayerTreeHostImpl> myHostImpl = createLayerTreeHost(true, context.release(), FakeLayerWithQuads::create(1)); + + // The first frame is not a partially-swapped one. + harness.mustSetScissor(0, 0, 10, 10); + harness.mustDrawSolidQuad(); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + Mock::VerifyAndClearExpectations(&mockContext); + + // Damage a portion of the frame. + myHostImpl->rootLayer()->setUpdateRect(IntRect(0, 0, 2, 3)); + + // The second frame will be partially-swapped (the y coordinates are flipped). + harness.mustSetScissor(0, 7, 2, 3); + harness.mustDrawSolidQuad(); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + Mock::VerifyAndClearExpectations(&mockContext); +} + +class PartialSwapContext : public FakeWebGraphicsContext3D { +public: + WebString getString(WGC3Denum name) + { + if (name == GraphicsContext3D::EXTENSIONS) + return WebString("GL_CHROMIUM_post_sub_buffer"); + return WebString(); + } + + WebString getRequestableExtensionsCHROMIUM() + { + return WebString("GL_CHROMIUM_post_sub_buffer"); + } + + // Unlimited texture size. + virtual void getIntegerv(WGC3Denum pname, WGC3Dint* value) + { + if (pname == WebCore::GraphicsContext3D::MAX_TEXTURE_SIZE) + *value = 8192; + } +}; + +static PassOwnPtr<CCLayerTreeHostImpl> setupLayersForOpacity(bool partialSwap, CCLayerTreeHostImplClient* client) +{ + CCSettings::setPartialSwapEnabled(partialSwap); + + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new PartialSwapContext)); + + CCLayerTreeSettings settings; + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, client); + myHostImpl->initializeRenderer(context.release(), UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(100, 100), IntSize(100, 100)); + + /* + Layers are created as follows: + + +--------------------+ + | 1 | + | +-----------+ | + | | 2 | | + | | +-------------------+ + | | | 3 | + | | +-------------------+ + | | | | + | +-----------+ | + | | + | | + +--------------------+ + + Layers 1, 2 have render surfaces + */ + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + OwnPtr<CCLayerImpl> child = CCLayerImpl::create(2); + OwnPtr<CCLayerImpl> grandChild = FakeLayerWithQuads::create(3); + + IntRect rootRect(0, 0, 100, 100); + IntRect childRect(10, 10, 50, 50); + IntRect grandChildRect(5, 5, 150, 150); + + root->createRenderSurface(); + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(rootRect.x(), rootRect.y())); + root->setBounds(IntSize(rootRect.width(), rootRect.height())); + root->setContentBounds(root->bounds()); + root->setVisibleContentRect(rootRect); + root->setDrawsContent(false); + root->renderSurface()->setContentRect(IntRect(IntPoint(), IntSize(rootRect.width(), rootRect.height()))); + + child->setAnchorPoint(FloatPoint(0, 0)); + child->setPosition(FloatPoint(childRect.x(), childRect.y())); + child->setOpacity(0.5f); + child->setBounds(IntSize(childRect.width(), childRect.height())); + child->setContentBounds(child->bounds()); + child->setVisibleContentRect(childRect); + child->setDrawsContent(false); + + grandChild->setAnchorPoint(FloatPoint(0, 0)); + grandChild->setPosition(IntPoint(grandChildRect.x(), grandChildRect.y())); + grandChild->setBounds(IntSize(grandChildRect.width(), grandChildRect.height())); + grandChild->setContentBounds(grandChild->bounds()); + grandChild->setVisibleContentRect(grandChildRect); + grandChild->setDrawsContent(true); + + child->addChild(grandChild.release()); + root->addChild(child.release()); + + myHostImpl->setRootLayer(root.release()); + return myHostImpl.release(); +} + +TEST_F(CCLayerTreeHostImplTest, contributingLayerEmptyScissorPartialSwap) +{ + OwnPtr<CCLayerTreeHostImpl> myHostImpl = setupLayersForOpacity(true, this); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Just for consistency, the most interesting stuff already happened + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + + // Verify all quads have been computed + ASSERT_EQ(2U, frame.renderPasses.size()); + ASSERT_EQ(1U, frame.renderPasses[0]->quadList().size()); + ASSERT_EQ(1U, frame.renderPasses[1]->quadList().size()); + EXPECT_EQ(CCDrawQuad::SolidColor, frame.renderPasses[0]->quadList()[0]->material()); + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + } +} + +TEST_F(CCLayerTreeHostImplTest, contributingLayerEmptyScissorNoPartialSwap) +{ + OwnPtr<CCLayerTreeHostImpl> myHostImpl = setupLayersForOpacity(false, this); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Just for consistency, the most interesting stuff already happened + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + + // Verify all quads have been computed + ASSERT_EQ(2U, frame.renderPasses.size()); + ASSERT_EQ(1U, frame.renderPasses[0]->quadList().size()); + ASSERT_EQ(1U, frame.renderPasses[1]->quadList().size()); + EXPECT_EQ(CCDrawQuad::SolidColor, frame.renderPasses[0]->quadList()[0]->material()); + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + } +} + +// Make sure that context lost notifications are propagated through the tree. +class ContextLostNotificationCheckLayer : public CCLayerImpl { +public: + static PassOwnPtr<ContextLostNotificationCheckLayer> create(int id) { return adoptPtr(new ContextLostNotificationCheckLayer(id)); } + + virtual void didLoseContext() OVERRIDE + { + m_didLoseContextCalled = true; + } + + bool didLoseContextCalled() const { return m_didLoseContextCalled; } + +private: + explicit ContextLostNotificationCheckLayer(int id) + : CCLayerImpl(id) + , m_didLoseContextCalled(false) + { + } + + bool m_didLoseContextCalled; +}; + +TEST_F(CCLayerTreeHostImplTest, contextLostAndRestoredNotificationSentToAllLayers) +{ + m_hostImpl->setRootLayer(ContextLostNotificationCheckLayer::create(1)); + ContextLostNotificationCheckLayer* root = static_cast<ContextLostNotificationCheckLayer*>(m_hostImpl->rootLayer()); + + root->addChild(ContextLostNotificationCheckLayer::create(1)); + ContextLostNotificationCheckLayer* layer1 = static_cast<ContextLostNotificationCheckLayer*>(root->children()[0].get()); + + layer1->addChild(ContextLostNotificationCheckLayer::create(2)); + ContextLostNotificationCheckLayer* layer2 = static_cast<ContextLostNotificationCheckLayer*>(layer1->children()[0].get()); + + EXPECT_FALSE(root->didLoseContextCalled()); + EXPECT_FALSE(layer1->didLoseContextCalled()); + EXPECT_FALSE(layer2->didLoseContextCalled()); + + m_hostImpl->initializeRenderer(createContext(), UnthrottledUploader); + + EXPECT_TRUE(root->didLoseContextCalled()); + EXPECT_TRUE(layer1->didLoseContextCalled()); + EXPECT_TRUE(layer2->didLoseContextCalled()); +} + +TEST_F(CCLayerTreeHostImplTest, finishAllRenderingAfterContextLost) +{ + CCLayerTreeSettings settings; + m_hostImpl = CCLayerTreeHostImpl::create(settings, this); + + // The context initialization will fail, but we should still be able to call finishAllRendering() without any ill effects. + m_hostImpl->initializeRenderer(FakeWebCompositorOutputSurface::create(adoptPtr(new FakeWebGraphicsContext3DMakeCurrentFails)), UnthrottledUploader); + m_hostImpl->finishAllRendering(); +} + +// Fake WebGraphicsContext3D that will cause a failure if trying to use a +// resource that wasn't created by it (resources created by +// FakeWebGraphicsContext3D have an id of 1). +class StrictWebGraphicsContext3D : public FakeWebGraphicsContext3D { +public: + StrictWebGraphicsContext3D() + : FakeWebGraphicsContext3D() + { + m_nextTextureId = 7; // Start allocating texture ids larger than any other resource IDs so we can tell if someone's mixing up their resource types. + } + + virtual WebGLId createBuffer() { return 2; } + virtual WebGLId createFramebuffer() { return 3; } + virtual WebGLId createProgram() { return 4; } + virtual WebGLId createRenderbuffer() { return 5; } + virtual WebGLId createShader(WGC3Denum) { return 6; } + + virtual void deleteBuffer(WebGLId id) + { + if (id != 2) + ADD_FAILURE() << "Trying to delete buffer id " << id; + } + + virtual void deleteFramebuffer(WebGLId id) + { + if (id != 3) + ADD_FAILURE() << "Trying to delete framebuffer id " << id; + } + + virtual void deleteProgram(WebGLId id) + { + if (id != 4) + ADD_FAILURE() << "Trying to delete program id " << id; + } + + virtual void deleteRenderbuffer(WebGLId id) + { + if (id != 5) + ADD_FAILURE() << "Trying to delete renderbuffer id " << id; + } + + virtual void deleteShader(WebGLId id) + { + if (id != 6) + ADD_FAILURE() << "Trying to delete shader id " << id; + } + + virtual WebGLId createTexture() + { + unsigned textureId = FakeWebGraphicsContext3D::createTexture(); + m_allocatedTextureIds.add(textureId); + return textureId; + } + virtual void deleteTexture(WebGLId id) + { + if (!m_allocatedTextureIds.contains(id)) + ADD_FAILURE() << "Trying to delete texture id " << id; + m_allocatedTextureIds.remove(id); + } + + virtual void bindBuffer(WGC3Denum, WebGLId id) + { + if (id != 2 && id) + ADD_FAILURE() << "Trying to bind buffer id " << id; + } + + virtual void bindFramebuffer(WGC3Denum, WebGLId id) + { + if (id != 3 && id) + ADD_FAILURE() << "Trying to bind framebuffer id " << id; + } + + virtual void useProgram(WebGLId id) + { + if (id != 4) + ADD_FAILURE() << "Trying to use program id " << id; + } + + virtual void bindRenderbuffer(WGC3Denum, WebGLId id) + { + if (id != 5 && id) + ADD_FAILURE() << "Trying to bind renderbuffer id " << id; + } + + virtual void attachShader(WebGLId program, WebGLId shader) + { + if ((program != 4) || (shader != 6)) + ADD_FAILURE() << "Trying to attach shader id " << shader << " to program id " << program; + } + + virtual void bindTexture(WGC3Denum, WebGLId id) + { + if (id && !m_allocatedTextureIds.contains(id)) + ADD_FAILURE() << "Trying to bind texture id " << id; + } + +private: + HashSet<unsigned> m_allocatedTextureIds; +}; + +// Fake video frame that represents a 4x4 YUV video frame. +class FakeVideoFrame: public WebVideoFrame { +public: + FakeVideoFrame() : m_textureId(0) { memset(m_data, 0x80, sizeof(m_data)); } + virtual ~FakeVideoFrame() { } + virtual Format format() const { return m_textureId ? FormatNativeTexture : FormatYV12; } + virtual unsigned width() const { return 4; } + virtual unsigned height() const { return 4; } + virtual unsigned planes() const { return 3; } + virtual int stride(unsigned plane) const { return 4; } + virtual const void* data(unsigned plane) const { return m_data; } + virtual unsigned textureId() const { return m_textureId; } + virtual unsigned textureTarget() const { return m_textureId ? GraphicsContext3D::TEXTURE_2D : 0; } + + void setTextureId(unsigned id) { m_textureId = id; } + +private: + char m_data[16]; + unsigned m_textureId; +}; + +// Fake video frame provider that always provides the same FakeVideoFrame. +class FakeVideoFrameProvider: public WebVideoFrameProvider { +public: + FakeVideoFrameProvider() : m_frame(0), m_client(0) { } + virtual ~FakeVideoFrameProvider() + { + if (m_client) + m_client->stopUsingProvider(); + } + + virtual void setVideoFrameProviderClient(Client* client) { m_client = client; } + virtual WebVideoFrame* getCurrentFrame() { return m_frame; } + virtual void putCurrentFrame(WebVideoFrame*) { } + + void setFrame(WebVideoFrame* frame) { m_frame = frame; } + +private: + WebVideoFrame* m_frame; + Client* m_client; +}; + +class StrictWebGraphicsContext3DWithIOSurface : public StrictWebGraphicsContext3D { +public: + virtual WebString getString(WGC3Denum name) OVERRIDE + { + if (name == WebCore::GraphicsContext3D::EXTENSIONS) + return WebString("GL_CHROMIUM_iosurface GL_ARB_texture_rectangle"); + + return WebString(); + } +}; + +class FakeWebGraphicsContext3DWithIOSurface : public FakeWebGraphicsContext3D { +public: + virtual WebString getString(WGC3Denum name) OVERRIDE + { + if (name == WebCore::GraphicsContext3D::EXTENSIONS) + return WebString("GL_CHROMIUM_iosurface GL_ARB_texture_rectangle"); + + return WebString(); + } +}; + +class FakeWebScrollbarThemeGeometryNonEmpty : public FakeWebScrollbarThemeGeometry { + virtual WebRect trackRect(WebScrollbar*) OVERRIDE { return WebRect(0, 0, 10, 10); } + virtual WebRect thumbRect(WebScrollbar*) OVERRIDE { return WebRect(0, 5, 5, 2); } + virtual void splitTrack(WebScrollbar*, const WebRect& track, WebRect& startTrack, WebRect& thumb, WebRect& endTrack) OVERRIDE + { + thumb = WebRect(0, 5, 5, 2); + startTrack = WebRect(0, 5, 0, 5); + endTrack = WebRect(0, 0, 0, 5); + } +}; + +class FakeScrollbarLayerImpl : public CCScrollbarLayerImpl { +public: + static PassOwnPtr<FakeScrollbarLayerImpl> create(int id) + { + return adoptPtr(new FakeScrollbarLayerImpl(id)); + } + + void createResources(CCResourceProvider* provider) + { + ASSERT(provider); + int pool = 0; + IntSize size(10, 10); + GC3Denum format = GraphicsContext3D::RGBA; + CCResourceProvider::TextureUsageHint hint = CCResourceProvider::TextureUsageAny; + setScrollbarGeometry(FakeWebScrollbarThemeGeometryNonEmpty::create()); + + setBackTrackResourceId(provider->createResource(pool, size, format, hint)); + setForeTrackResourceId(provider->createResource(pool, size, format, hint)); + setThumbResourceId(provider->createResource(pool, size, format, hint)); + } + +protected: + explicit FakeScrollbarLayerImpl(int id) + : CCScrollbarLayerImpl(id) + { + } +}; + +TEST_F(CCLayerTreeHostImplTest, dontUseOldResourcesAfterLostContext) +{ + OwnPtr<CCLayerImpl> rootLayer(CCLayerImpl::create(1)); + rootLayer->setBounds(IntSize(10, 10)); + rootLayer->setAnchorPoint(FloatPoint(0, 0)); + + OwnPtr<CCTiledLayerImpl> tileLayer = CCTiledLayerImpl::create(2); + tileLayer->setBounds(IntSize(10, 10)); + tileLayer->setAnchorPoint(FloatPoint(0, 0)); + tileLayer->setContentBounds(IntSize(10, 10)); + tileLayer->setDrawsContent(true); + tileLayer->setSkipsDraw(false); + OwnPtr<CCLayerTilingData> tilingData(CCLayerTilingData::create(IntSize(10, 10), CCLayerTilingData::NoBorderTexels)); + tilingData->setBounds(IntSize(10, 10)); + tileLayer->setTilingData(*tilingData); + tileLayer->pushTileProperties(0, 0, 1, IntRect(0, 0, 10, 10)); + rootLayer->addChild(tileLayer.release()); + + OwnPtr<CCTextureLayerImpl> textureLayer = CCTextureLayerImpl::create(3); + textureLayer->setBounds(IntSize(10, 10)); + textureLayer->setAnchorPoint(FloatPoint(0, 0)); + textureLayer->setContentBounds(IntSize(10, 10)); + textureLayer->setDrawsContent(true); + textureLayer->setTextureId(1); + rootLayer->addChild(textureLayer.release()); + + FakeVideoFrame videoFrame; + FakeVideoFrameProvider provider; + provider.setFrame(&videoFrame); + OwnPtr<CCVideoLayerImpl> videoLayer = CCVideoLayerImpl::create(4, &provider); + videoLayer->setBounds(IntSize(10, 10)); + videoLayer->setAnchorPoint(FloatPoint(0, 0)); + videoLayer->setContentBounds(IntSize(10, 10)); + videoLayer->setDrawsContent(true); + videoLayer->setLayerTreeHostImpl(m_hostImpl.get()); + rootLayer->addChild(videoLayer.release()); + + FakeVideoFrame hwVideoFrame; + FakeVideoFrameProvider hwProvider; + hwProvider.setFrame(&hwVideoFrame); + OwnPtr<CCVideoLayerImpl> hwVideoLayer = CCVideoLayerImpl::create(5, &hwProvider); + hwVideoLayer->setBounds(IntSize(10, 10)); + hwVideoLayer->setAnchorPoint(FloatPoint(0, 0)); + hwVideoLayer->setContentBounds(IntSize(10, 10)); + hwVideoLayer->setDrawsContent(true); + hwVideoLayer->setLayerTreeHostImpl(m_hostImpl.get()); + rootLayer->addChild(hwVideoLayer.release()); + + OwnPtr<CCIOSurfaceLayerImpl> ioSurfaceLayer = CCIOSurfaceLayerImpl::create(6); + ioSurfaceLayer->setBounds(IntSize(10, 10)); + ioSurfaceLayer->setAnchorPoint(FloatPoint(0, 0)); + ioSurfaceLayer->setContentBounds(IntSize(10, 10)); + ioSurfaceLayer->setDrawsContent(true); + ioSurfaceLayer->setIOSurfaceProperties(1, IntSize(10, 10)); + ioSurfaceLayer->setLayerTreeHostImpl(m_hostImpl.get()); + rootLayer->addChild(ioSurfaceLayer.release()); + + OwnPtr<CCHeadsUpDisplayLayerImpl> hudLayer = CCHeadsUpDisplayLayerImpl::create(7); + hudLayer->setBounds(IntSize(10, 10)); + hudLayer->setAnchorPoint(FloatPoint(0, 0)); + hudLayer->setContentBounds(IntSize(10, 10)); + hudLayer->setDrawsContent(true); + hudLayer->setLayerTreeHostImpl(m_hostImpl.get()); + rootLayer->addChild(hudLayer.release()); + + OwnPtr<FakeScrollbarLayerImpl> scrollbarLayer(FakeScrollbarLayerImpl::create(8)); + scrollbarLayer->setLayerTreeHostImpl(m_hostImpl.get()); + scrollbarLayer->setBounds(IntSize(10, 10)); + scrollbarLayer->setContentBounds(IntSize(10, 10)); + scrollbarLayer->setDrawsContent(true); + scrollbarLayer->setLayerTreeHostImpl(m_hostImpl.get()); + scrollbarLayer->createResources(m_hostImpl->resourceProvider()); + rootLayer->addChild(scrollbarLayer.release()); + + // Use a context that supports IOSurfaces + m_hostImpl->initializeRenderer(FakeWebCompositorOutputSurface::create(adoptPtr(new FakeWebGraphicsContext3DWithIOSurface)), UnthrottledUploader); + + hwVideoFrame.setTextureId(m_hostImpl->resourceProvider()->graphicsContext3D()->createTexture()); + + m_hostImpl->setRootLayer(rootLayer.release()); + + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + m_hostImpl->swapBuffers(); + + unsigned numResources = m_hostImpl->resourceProvider()->numResources(); + + // Lose the context, replacing it with a StrictWebGraphicsContext3DWithIOSurface, + // that will warn if any resource from the previous context gets used. + m_hostImpl->initializeRenderer(FakeWebCompositorOutputSurface::create(adoptPtr(new StrictWebGraphicsContext3DWithIOSurface)), UnthrottledUploader); + + // Create dummy resources so that looking up an old resource will get an + // invalid texture id mapping. + for (unsigned i = 0; i < numResources; ++i) + m_hostImpl->resourceProvider()->createResourceFromExternalTexture(1); + + // The WebVideoFrameProvider is expected to recreate its textures after a + // lost context (or not serve a frame). + hwProvider.setFrame(0); + + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + m_hostImpl->swapBuffers(); + + hwVideoFrame.setTextureId(m_hostImpl->resourceProvider()->graphicsContext3D()->createTexture()); + hwProvider.setFrame(&hwVideoFrame); + + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + m_hostImpl->swapBuffers(); +} + +// Fake WebGraphicsContext3D that tracks the number of textures in use. +class TrackingWebGraphicsContext3D : public FakeWebGraphicsContext3D { +public: + TrackingWebGraphicsContext3D() + : FakeWebGraphicsContext3D() + , m_numTextures(0) + { } + + virtual WebGLId createTexture() OVERRIDE + { + WebGLId id = FakeWebGraphicsContext3D::createTexture(); + + m_textures.set(id, true); + ++m_numTextures; + return id; + } + + virtual void deleteTexture(WebGLId id) OVERRIDE + { + if (!m_textures.get(id)) + return; + + m_textures.set(id, false); + --m_numTextures; + } + + virtual WebString getString(WGC3Denum name) OVERRIDE + { + if (name == WebCore::GraphicsContext3D::EXTENSIONS) + return WebString("GL_CHROMIUM_iosurface GL_ARB_texture_rectangle"); + + return WebString(); + } + + unsigned numTextures() const { return m_numTextures; } + +private: + HashMap<WebGLId, bool> m_textures; + unsigned m_numTextures; +}; + +TEST_F(CCLayerTreeHostImplTest, layersFreeTextures) +{ + OwnPtr<CCLayerImpl> rootLayer(CCLayerImpl::create(1)); + rootLayer->setBounds(IntSize(10, 10)); + rootLayer->setAnchorPoint(FloatPoint(0, 0)); + + OwnPtr<CCTiledLayerImpl> tileLayer = CCTiledLayerImpl::create(2); + tileLayer->setBounds(IntSize(10, 10)); + tileLayer->setAnchorPoint(FloatPoint(0, 0)); + tileLayer->setContentBounds(IntSize(10, 10)); + tileLayer->setDrawsContent(true); + tileLayer->setSkipsDraw(false); + OwnPtr<CCLayerTilingData> tilingData(CCLayerTilingData::create(IntSize(10, 10), CCLayerTilingData::NoBorderTexels)); + tilingData->setBounds(IntSize(10, 10)); + tileLayer->setTilingData(*tilingData); + tileLayer->pushTileProperties(0, 0, 1, IntRect(0, 0, 10, 10)); + rootLayer->addChild(tileLayer.release()); + + OwnPtr<CCTextureLayerImpl> textureLayer = CCTextureLayerImpl::create(3); + textureLayer->setBounds(IntSize(10, 10)); + textureLayer->setAnchorPoint(FloatPoint(0, 0)); + textureLayer->setContentBounds(IntSize(10, 10)); + textureLayer->setDrawsContent(true); + textureLayer->setTextureId(1); + rootLayer->addChild(textureLayer.release()); + + FakeVideoFrameProvider provider; + OwnPtr<CCVideoLayerImpl> videoLayer = CCVideoLayerImpl::create(4, &provider); + videoLayer->setBounds(IntSize(10, 10)); + videoLayer->setAnchorPoint(FloatPoint(0, 0)); + videoLayer->setContentBounds(IntSize(10, 10)); + videoLayer->setDrawsContent(true); + videoLayer->setLayerTreeHostImpl(m_hostImpl.get()); + rootLayer->addChild(videoLayer.release()); + + OwnPtr<CCIOSurfaceLayerImpl> ioSurfaceLayer = CCIOSurfaceLayerImpl::create(5); + ioSurfaceLayer->setBounds(IntSize(10, 10)); + ioSurfaceLayer->setAnchorPoint(FloatPoint(0, 0)); + ioSurfaceLayer->setContentBounds(IntSize(10, 10)); + ioSurfaceLayer->setDrawsContent(true); + ioSurfaceLayer->setIOSurfaceProperties(1, IntSize(10, 10)); + ioSurfaceLayer->setLayerTreeHostImpl(m_hostImpl.get()); + rootLayer->addChild(ioSurfaceLayer.release()); + + // Lose the context, replacing it with a TrackingWebGraphicsContext3D (which the CCLayerTreeHostImpl takes ownership of). + OwnPtr<CCGraphicsContext> ccContext(FakeWebCompositorOutputSurface::create(adoptPtr(new TrackingWebGraphicsContext3D))); + TrackingWebGraphicsContext3D* trackingWebGraphicsContext = static_cast<TrackingWebGraphicsContext3D*>(ccContext->context3D()); + m_hostImpl->initializeRenderer(ccContext.release(), UnthrottledUploader); + + m_hostImpl->setRootLayer(rootLayer.release()); + + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + m_hostImpl->swapBuffers(); + + EXPECT_GT(trackingWebGraphicsContext->numTextures(), 0u); + + // Kill the layer tree. + m_hostImpl->setRootLayer(CCLayerImpl::create(100)); + // There should be no textures left in use after. + EXPECT_EQ(0u, trackingWebGraphicsContext->numTextures()); +} + +class MockDrawQuadsToFillScreenContext : public FakeWebGraphicsContext3D { +public: + MOCK_METHOD1(useProgram, void(WebGLId program)); + MOCK_METHOD4(drawElements, void(WGC3Denum mode, WGC3Dsizei count, WGC3Denum type, WGC3Dintptr offset)); +}; + +TEST_F(CCLayerTreeHostImplTest, hasTransparentBackground) +{ + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new MockDrawQuadsToFillScreenContext)); + MockDrawQuadsToFillScreenContext* mockContext = static_cast<MockDrawQuadsToFillScreenContext*>(context->context3D()); + + // Run test case + OwnPtr<CCLayerTreeHostImpl> myHostImpl = createLayerTreeHost(false, context.release(), CCLayerImpl::create(1)); + myHostImpl->setBackgroundColor(SK_ColorWHITE); + + // Verify one quad is drawn when transparent background set is not set. + myHostImpl->setHasTransparentBackground(false); + EXPECT_CALL(*mockContext, useProgram(_)) + .Times(1); + EXPECT_CALL(*mockContext, drawElements(_, _, _, _)) + .Times(1); + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + Mock::VerifyAndClearExpectations(&mockContext); + + // Verify no quads are drawn when transparent background is set. + myHostImpl->setHasTransparentBackground(true); + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + Mock::VerifyAndClearExpectations(&mockContext); +} + +static void addDrawingLayerTo(CCLayerImpl* parent, int id, const IntRect& layerRect, CCLayerImpl** result) +{ + OwnPtr<CCLayerImpl> layer = FakeLayerWithQuads::create(id); + CCLayerImpl* layerPtr = layer.get(); + layerPtr->setAnchorPoint(FloatPoint(0, 0)); + layerPtr->setPosition(FloatPoint(layerRect.location())); + layerPtr->setBounds(layerRect.size()); + layerPtr->setContentBounds(layerRect.size()); + layerPtr->setDrawsContent(true); // only children draw content + layerPtr->setOpaque(true); + parent->addChild(layer.release()); + if (result) + *result = layerPtr; +} + +static void setupLayersForTextureCaching(CCLayerTreeHostImpl* layerTreeHostImpl, CCLayerImpl*& rootPtr, CCLayerImpl*& intermediateLayerPtr, CCLayerImpl*& surfaceLayerPtr, CCLayerImpl*& childPtr, const IntSize& rootSize) +{ + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new PartialSwapContext)); + + layerTreeHostImpl->initializeRenderer(context.release(), UnthrottledUploader); + layerTreeHostImpl->setViewportSize(rootSize, rootSize); + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + rootPtr = root.get(); + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(0, 0)); + root->setBounds(rootSize); + root->setContentBounds(rootSize); + root->setDrawsContent(true); + layerTreeHostImpl->setRootLayer(root.release()); + + addDrawingLayerTo(rootPtr, 2, IntRect(10, 10, rootSize.width(), rootSize.height()), &intermediateLayerPtr); + intermediateLayerPtr->setDrawsContent(false); // only children draw content + + // Surface layer is the layer that changes its opacity + // It will contain other layers that draw content. + addDrawingLayerTo(intermediateLayerPtr, 3, IntRect(10, 10, rootSize.width(), rootSize.height()), &surfaceLayerPtr); + surfaceLayerPtr->setDrawsContent(false); // only children draw content + surfaceLayerPtr->setOpacity(0.5f); // This will cause it to have a surface + + // Child of the surface layer will produce some quads + addDrawingLayerTo(surfaceLayerPtr, 4, IntRect(5, 5, rootSize.width() - 25, rootSize.height() - 25), &childPtr); +} + +class CCRendererGLWithReleaseTextures : public CCRendererGL { +public: + using CCRendererGL::releaseRenderPassTextures; +}; + +TEST_F(CCLayerTreeHostImplTest, textureCachingWithClipping) +{ + CCSettings::setPartialSwapEnabled(true); + + CCLayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = IntSize(); + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + CCLayerImpl* rootPtr; + CCLayerImpl* surfaceLayerPtr; + + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new PartialSwapContext)); + + IntSize rootSize(100, 100); + + myHostImpl->initializeRenderer(context.release(), UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(rootSize.width(), rootSize.height()), IntSize(rootSize.width(), rootSize.height())); + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + rootPtr = root.get(); + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(0, 0)); + root->setBounds(rootSize); + root->setContentBounds(rootSize); + root->setDrawsContent(true); + root->setMasksToBounds(true); + myHostImpl->setRootLayer(root.release()); + + addDrawingLayerTo(rootPtr, 3, IntRect(0, 0, rootSize.width(), rootSize.height()), &surfaceLayerPtr); + surfaceLayerPtr->setDrawsContent(false); + + // Surface layer is the layer that changes its opacity + // It will contain other layers that draw content. + surfaceLayerPtr->setOpacity(0.5f); // This will cause it to have a surface + + addDrawingLayerTo(surfaceLayerPtr, 4, IntRect(0, 0, 100, 3), 0); + addDrawingLayerTo(surfaceLayerPtr, 5, IntRect(0, 97, 100, 3), 0); + + // Rotation will put part of the child ouside the bounds of the root layer. + // Nevertheless, the child layers should be drawn. + WebTransformationMatrix transform = surfaceLayerPtr->transform(); + transform.translate(50, 50); + transform.rotate(35); + transform.translate(-50, -50); + surfaceLayerPtr->setTransform(transform); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes, each with one quad + ASSERT_EQ(2U, frame.renderPasses.size()); + EXPECT_EQ(2U, frame.renderPasses[0]->quadList().size()); + ASSERT_EQ(1U, frame.renderPasses[1]->quadList().size()); + + // Verify that the child layers are being clipped. + IntRect quadVisibleRect = frame.renderPasses[0]->quadList()[0]->quadVisibleRect(); + EXPECT_LT(quadVisibleRect.width(), 100); + + quadVisibleRect = frame.renderPasses[0]->quadList()[1]->quadVisibleRect(); + EXPECT_LT(quadVisibleRect.width(), 100); + + // Verify that the render surface texture is *not* clipped. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 100), frame.renderPasses[0]->outputRect()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get()); + EXPECT_FALSE(quad->contentsChangedSinceLastFrame().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + transform = surfaceLayerPtr->transform(); + transform.translate(50, 50); + transform.rotate(-35); + transform.translate(-50, -50); + surfaceLayerPtr->setTransform(transform); + + // The surface is now aligned again, and the clipped parts are exposed. + // Since the layers were clipped, even though the render surface size + // was not changed, the texture should not be saved. + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes, each with one quad + ASSERT_EQ(2U, frame.renderPasses.size()); + EXPECT_EQ(2U, frame.renderPasses[0]->quadList().size()); + ASSERT_EQ(1U, frame.renderPasses[1]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } +} + +TEST_F(CCLayerTreeHostImplTest, textureCachingWithOcclusion) +{ + CCSettings::setPartialSwapEnabled(false); + + CCLayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = IntSize(); + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + // Layers are structure as follows: + // + // R +-- S1 +- L10 (owning) + // | +- L11 + // | +- L12 + // | + // +-- S2 +- L20 (owning) + // +- L21 + // + // Occlusion: + // L12 occludes L11 (internal) + // L20 occludes L10 (external) + // L21 occludes L20 (internal) + + CCLayerImpl* rootPtr; + CCLayerImpl* layerS1Ptr; + CCLayerImpl* layerS2Ptr; + + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new PartialSwapContext)); + + IntSize rootSize(1000, 1000); + + myHostImpl->initializeRenderer(context.release(), UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(rootSize.width(), rootSize.height()), IntSize(rootSize.width(), rootSize.height())); + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + rootPtr = root.get(); + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(0, 0)); + root->setBounds(rootSize); + root->setContentBounds(rootSize); + root->setDrawsContent(true); + root->setMasksToBounds(true); + myHostImpl->setRootLayer(root.release()); + + addDrawingLayerTo(rootPtr, 2, IntRect(300, 300, 300, 300), &layerS1Ptr); + layerS1Ptr->setForceRenderSurface(true); + + addDrawingLayerTo(layerS1Ptr, 3, IntRect(10, 10, 10, 10), 0); // L11 + addDrawingLayerTo(layerS1Ptr, 4, IntRect(0, 0, 30, 30), 0); // L12 + + addDrawingLayerTo(rootPtr, 5, IntRect(550, 250, 300, 400), &layerS2Ptr); + layerS2Ptr->setForceRenderSurface(true); + + addDrawingLayerTo(layerS2Ptr, 6, IntRect(20, 20, 5, 5), 0); // L21 + + // Initial draw - must receive all quads + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 3 render passes. + // For Root, there are 2 quads; for S1, there are 2 quads (1 is occluded); for S2, there is 2 quads. + ASSERT_EQ(3U, frame.renderPasses.size()); + + EXPECT_EQ(2U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(2U, frame.renderPasses[1]->quadList().size()); + EXPECT_EQ(2U, frame.renderPasses[2]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // "Unocclude" surface S1 and repeat draw. + // Must remove S2's render pass since it's cached; + // Must keep S1 quads because texture contained external occlusion. + WebTransformationMatrix transform = layerS2Ptr->transform(); + transform.translate(150, 150); + layerS2Ptr->setTransform(transform); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 2 render passes. + // For Root, there are 2 quads + // For S1, the number of quads depends on what got unoccluded, so not asserted beyond being positive. + // For S2, there is no render pass + ASSERT_EQ(2U, frame.renderPasses.size()); + + EXPECT_GT(frame.renderPasses[0]->quadList().size(), 0U); + EXPECT_EQ(2U, frame.renderPasses[1]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // "Re-occlude" surface S1 and repeat draw. + // Must remove S1's render pass since it is now available in full. + // S2 has no change so must also be removed. + transform = layerS2Ptr->transform(); + transform.translate(-15, -15); + layerS2Ptr->setTransform(transform); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 1 render pass - for the root. + ASSERT_EQ(1U, frame.renderPasses.size()); + + EXPECT_EQ(2U, frame.renderPasses[0]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + +} + +TEST_F(CCLayerTreeHostImplTest, textureCachingWithOcclusionEarlyOut) +{ + CCSettings::setPartialSwapEnabled(false); + + CCLayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = IntSize(); + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + // Layers are structure as follows: + // + // R +-- S1 +- L10 (owning, non drawing) + // | +- L11 (corner, unoccluded) + // | +- L12 (corner, unoccluded) + // | +- L13 (corner, unoccluded) + // | +- L14 (corner, entirely occluded) + // | + // +-- S2 +- L20 (owning, drawing) + // + + CCLayerImpl* rootPtr; + CCLayerImpl* layerS1Ptr; + CCLayerImpl* layerS2Ptr; + + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new PartialSwapContext)); + + IntSize rootSize(1000, 1000); + + myHostImpl->initializeRenderer(context.release(), UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(rootSize.width(), rootSize.height()), IntSize(rootSize.width(), rootSize.height())); + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + rootPtr = root.get(); + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(0, 0)); + root->setBounds(rootSize); + root->setContentBounds(rootSize); + root->setDrawsContent(true); + root->setMasksToBounds(true); + myHostImpl->setRootLayer(root.release()); + + addDrawingLayerTo(rootPtr, 2, IntRect(0, 0, 800, 800), &layerS1Ptr); + layerS1Ptr->setForceRenderSurface(true); + layerS1Ptr->setDrawsContent(false); + + addDrawingLayerTo(layerS1Ptr, 3, IntRect(0, 0, 300, 300), 0); // L11 + addDrawingLayerTo(layerS1Ptr, 4, IntRect(0, 500, 300, 300), 0); // L12 + addDrawingLayerTo(layerS1Ptr, 5, IntRect(500, 0, 300, 300), 0); // L13 + addDrawingLayerTo(layerS1Ptr, 6, IntRect(500, 500, 300, 300), 0); // L14 + addDrawingLayerTo(layerS1Ptr, 9, IntRect(500, 500, 300, 300), 0); // L14 + + addDrawingLayerTo(rootPtr, 7, IntRect(450, 450, 450, 450), &layerS2Ptr); + layerS2Ptr->setForceRenderSurface(true); + + // Initial draw - must receive all quads + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 3 render passes. + // For Root, there are 2 quads; for S1, there are 3 quads; for S2, there is 1 quad. + ASSERT_EQ(3U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + + // L14 is culled, so only 3 quads. + EXPECT_EQ(3U, frame.renderPasses[1]->quadList().size()); + EXPECT_EQ(2U, frame.renderPasses[2]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // "Unocclude" surface S1 and repeat draw. + // Must remove S2's render pass since it's cached; + // Must keep S1 quads because texture contained external occlusion. + WebTransformationMatrix transform = layerS2Ptr->transform(); + transform.translate(100, 100); + layerS2Ptr->setTransform(transform); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 2 render passes. + // For Root, there are 2 quads + // For S1, the number of quads depends on what got unoccluded, so not asserted beyond being positive. + // For S2, there is no render pass + ASSERT_EQ(2U, frame.renderPasses.size()); + + EXPECT_GT(frame.renderPasses[0]->quadList().size(), 0U); + EXPECT_EQ(2U, frame.renderPasses[1]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // "Re-occlude" surface S1 and repeat draw. + // Must remove S1's render pass since it is now available in full. + // S2 has no change so must also be removed. + transform = layerS2Ptr->transform(); + transform.translate(-15, -15); + layerS2Ptr->setTransform(transform); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 1 render pass - for the root. + ASSERT_EQ(1U, frame.renderPasses.size()); + + EXPECT_EQ(2U, frame.renderPasses[0]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } +} + +TEST_F(CCLayerTreeHostImplTest, textureCachingWithOcclusionExternalOverInternal) +{ + CCSettings::setPartialSwapEnabled(false); + + CCLayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = IntSize(); + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + // Layers are structured as follows: + // + // R +-- S1 +- L10 (owning, drawing) + // | +- L11 (corner, occluded by L12) + // | +- L12 (opposite corner) + // | + // +-- S2 +- L20 (owning, drawing) + // + + CCLayerImpl* rootPtr; + CCLayerImpl* layerS1Ptr; + CCLayerImpl* layerS2Ptr; + + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new PartialSwapContext)); + + IntSize rootSize(1000, 1000); + + myHostImpl->initializeRenderer(context.release(), UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(rootSize.width(), rootSize.height()), IntSize(rootSize.width(), rootSize.height())); + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + rootPtr = root.get(); + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(0, 0)); + root->setBounds(rootSize); + root->setContentBounds(rootSize); + root->setDrawsContent(true); + root->setMasksToBounds(true); + myHostImpl->setRootLayer(root.release()); + + addDrawingLayerTo(rootPtr, 2, IntRect(0, 0, 400, 400), &layerS1Ptr); + layerS1Ptr->setForceRenderSurface(true); + + addDrawingLayerTo(layerS1Ptr, 3, IntRect(0, 0, 300, 300), 0); // L11 + addDrawingLayerTo(layerS1Ptr, 4, IntRect(100, 0, 300, 300), 0); // L12 + + addDrawingLayerTo(rootPtr, 7, IntRect(200, 0, 300, 300), &layerS2Ptr); + layerS2Ptr->setForceRenderSurface(true); + + // Initial draw - must receive all quads + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 3 render passes. + // For Root, there are 2 quads; for S1, there are 3 quads; for S2, there is 1 quad. + ASSERT_EQ(3U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(3U, frame.renderPasses[1]->quadList().size()); + EXPECT_EQ(2U, frame.renderPasses[2]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // "Unocclude" surface S1 and repeat draw. + // Must remove S2's render pass since it's cached; + // Must keep S1 quads because texture contained external occlusion. + WebTransformationMatrix transform = layerS2Ptr->transform(); + transform.translate(300, 0); + layerS2Ptr->setTransform(transform); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 2 render passes. + // For Root, there are 2 quads + // For S1, the number of quads depends on what got unoccluded, so not asserted beyond being positive. + // For S2, there is no render pass + ASSERT_EQ(2U, frame.renderPasses.size()); + + EXPECT_GT(frame.renderPasses[0]->quadList().size(), 0U); + EXPECT_EQ(2U, frame.renderPasses[1]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } +} + +TEST_F(CCLayerTreeHostImplTest, textureCachingWithOcclusionExternalNotAligned) +{ + CCSettings::setPartialSwapEnabled(false); + + CCLayerTreeSettings settings; + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + // Layers are structured as follows: + // + // R +-- S1 +- L10 (rotated, drawing) + // +- L11 (occupies half surface) + + CCLayerImpl* rootPtr; + CCLayerImpl* layerS1Ptr; + + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new PartialSwapContext)); + + IntSize rootSize(1000, 1000); + + myHostImpl->initializeRenderer(context.release(), UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(rootSize.width(), rootSize.height()), IntSize(rootSize.width(), rootSize.height())); + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + rootPtr = root.get(); + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(0, 0)); + root->setBounds(rootSize); + root->setContentBounds(rootSize); + root->setDrawsContent(true); + root->setMasksToBounds(true); + myHostImpl->setRootLayer(root.release()); + + addDrawingLayerTo(rootPtr, 2, IntRect(0, 0, 400, 400), &layerS1Ptr); + layerS1Ptr->setForceRenderSurface(true); + WebTransformationMatrix transform = layerS1Ptr->transform(); + transform.translate(200, 200); + transform.rotate(45); + transform.translate(-200, -200); + layerS1Ptr->setTransform(transform); + + addDrawingLayerTo(layerS1Ptr, 3, IntRect(200, 0, 200, 400), 0); // L11 + + // Initial draw - must receive all quads + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 2 render passes. + ASSERT_EQ(2U, frame.renderPasses.size()); + + EXPECT_EQ(2U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(1U, frame.renderPasses[1]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change opacity and draw. Verify we used cached texture. + layerS1Ptr->setOpacity(0.2f); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // One render pass must be gone due to cached texture. + ASSERT_EQ(1U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } +} + +TEST_F(CCLayerTreeHostImplTest, textureCachingWithOcclusionPartialSwap) +{ + CCSettings::setPartialSwapEnabled(true); + + CCLayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = IntSize(); + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + // Layers are structure as follows: + // + // R +-- S1 +- L10 (owning) + // | +- L11 + // | +- L12 + // | + // +-- S2 +- L20 (owning) + // +- L21 + // + // Occlusion: + // L12 occludes L11 (internal) + // L20 occludes L10 (external) + // L21 occludes L20 (internal) + + CCLayerImpl* rootPtr; + CCLayerImpl* layerS1Ptr; + CCLayerImpl* layerS2Ptr; + + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new PartialSwapContext)); + + IntSize rootSize(1000, 1000); + + myHostImpl->initializeRenderer(context.release(), UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(rootSize.width(), rootSize.height()), IntSize(rootSize.width(), rootSize.height())); + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + rootPtr = root.get(); + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(0, 0)); + root->setBounds(rootSize); + root->setContentBounds(rootSize); + root->setDrawsContent(true); + root->setMasksToBounds(true); + myHostImpl->setRootLayer(root.release()); + + addDrawingLayerTo(rootPtr, 2, IntRect(300, 300, 300, 300), &layerS1Ptr); + layerS1Ptr->setForceRenderSurface(true); + + addDrawingLayerTo(layerS1Ptr, 3, IntRect(10, 10, 10, 10), 0); // L11 + addDrawingLayerTo(layerS1Ptr, 4, IntRect(0, 0, 30, 30), 0); // L12 + + addDrawingLayerTo(rootPtr, 5, IntRect(550, 250, 300, 400), &layerS2Ptr); + layerS2Ptr->setForceRenderSurface(true); + + addDrawingLayerTo(layerS2Ptr, 6, IntRect(20, 20, 5, 5), 0); // L21 + + // Initial draw - must receive all quads + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 3 render passes. + // For Root, there are 2 quads; for S1, there are 2 quads (one is occluded); for S2, there is 2 quads. + ASSERT_EQ(3U, frame.renderPasses.size()); + + EXPECT_EQ(2U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(2U, frame.renderPasses[1]->quadList().size()); + EXPECT_EQ(2U, frame.renderPasses[2]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // "Unocclude" surface S1 and repeat draw. + // Must remove S2's render pass since it's cached; + // Must keep S1 quads because texture contained external occlusion. + WebTransformationMatrix transform = layerS2Ptr->transform(); + transform.translate(150, 150); + layerS2Ptr->setTransform(transform); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive 2 render passes. + // For Root, there are 2 quads. + // For S1, there are 2 quads. + // For S2, there is no render pass + ASSERT_EQ(2U, frame.renderPasses.size()); + + EXPECT_EQ(2U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(2U, frame.renderPasses[1]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // "Re-occlude" surface S1 and repeat draw. + // Must remove S1's render pass since it is now available in full. + // S2 has no change so must also be removed. + transform = layerS2Ptr->transform(); + transform.translate(-15, -15); + layerS2Ptr->setTransform(transform); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Root render pass only. + ASSERT_EQ(1U, frame.renderPasses.size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } +} + +TEST_F(CCLayerTreeHostImplTest, textureCachingWithScissor) +{ + CCSettings::setPartialSwapEnabled(false); + + CCLayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = IntSize(); + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + /* + Layers are created as follows: + + +--------------------+ + | 1 | + | +-----------+ | + | | 2 | | + | | +-------------------+ + | | | 3 | + | | +-------------------+ + | | | | + | +-----------+ | + | | + | | + +--------------------+ + + Layers 1, 2 have render surfaces + */ + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + OwnPtr<CCTiledLayerImpl> child = CCTiledLayerImpl::create(2); + OwnPtr<CCLayerImpl> grandChild = CCLayerImpl::create(3); + + IntRect rootRect(0, 0, 100, 100); + IntRect childRect(10, 10, 50, 50); + IntRect grandChildRect(5, 5, 150, 150); + + OwnPtr<CCGraphicsContext> context = FakeWebCompositorOutputSurface::create(adoptPtr(new PartialSwapContext)); + myHostImpl->initializeRenderer(context.release(), UnthrottledUploader); + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(rootRect.x(), rootRect.y())); + root->setBounds(IntSize(rootRect.width(), rootRect.height())); + root->setContentBounds(root->bounds()); + root->setDrawsContent(true); + root->setMasksToBounds(true); + + child->setAnchorPoint(FloatPoint(0, 0)); + child->setPosition(FloatPoint(childRect.x(), childRect.y())); + child->setOpacity(0.5); + child->setBounds(IntSize(childRect.width(), childRect.height())); + child->setContentBounds(child->bounds()); + child->setDrawsContent(true); + child->setSkipsDraw(false); + + // child layer has 10x10 tiles. + OwnPtr<CCLayerTilingData> tiler = CCLayerTilingData::create(IntSize(10, 10), CCLayerTilingData::HasBorderTexels); + tiler->setBounds(child->contentBounds()); + child->setTilingData(*tiler.get()); + + grandChild->setAnchorPoint(FloatPoint(0, 0)); + grandChild->setPosition(IntPoint(grandChildRect.x(), grandChildRect.y())); + grandChild->setBounds(IntSize(grandChildRect.width(), grandChildRect.height())); + grandChild->setContentBounds(grandChild->bounds()); + grandChild->setDrawsContent(true); + + CCTiledLayerImpl* childPtr = child.get(); + + child->addChild(grandChild.release()); + root->addChild(child.release()); + myHostImpl->setRootLayer(root.release()); + myHostImpl->setViewportSize(rootRect.size(), rootRect.size()); + + EXPECT_FALSE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPtr->id())); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // We should have cached textures for surface 2. + EXPECT_TRUE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPtr->id())); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // We should still have cached textures for surface 2 after drawing with no damage. + EXPECT_TRUE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPtr->id())); + + // Damage a single tile of surface 2. + childPtr->setUpdateRect(IntRect(10, 10, 10, 10)); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // We should have a cached texture for surface 2 again even though it was damaged. + EXPECT_TRUE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(childPtr->id())); +} + +TEST_F(CCLayerTreeHostImplTest, surfaceTextureCaching) +{ + CCSettings::setPartialSwapEnabled(true); + + CCLayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = IntSize(); + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + CCLayerImpl* rootPtr; + CCLayerImpl* intermediateLayerPtr; + CCLayerImpl* surfaceLayerPtr; + CCLayerImpl* childPtr; + + setupLayersForTextureCaching(myHostImpl.get(), rootPtr, intermediateLayerPtr, surfaceLayerPtr, childPtr, IntSize(100, 100)); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes, each with one quad + ASSERT_EQ(2U, frame.renderPasses.size()); + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(1U, frame.renderPasses[1]->quadList().size()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_FALSE(targetPass->damageRect().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Draw without any change + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive one render pass, as the other one should be culled + ASSERT_EQ(1U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[0]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[0]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_TRUE(targetPass->damageRect().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change opacity and draw + surfaceLayerPtr->setOpacity(0.6f); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive one render pass, as the other one should be culled + ASSERT_EQ(1U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[0]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[0]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_TRUE(targetPass->damageRect().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change less benign property and draw - should have contents changed flag + surfaceLayerPtr->setStackingOrderChanged(true); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes, each with one quad + ASSERT_EQ(2U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(CCDrawQuad::SolidColor, frame.renderPasses[0]->quadList()[0]->material()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_FALSE(targetPass->damageRect().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change opacity again, and evict the cached surface texture. + surfaceLayerPtr->setOpacity(0.5f); + static_cast<CCRendererGLWithReleaseTextures*>(myHostImpl->renderer())->releaseRenderPassTextures(); + + // Change opacity and draw + surfaceLayerPtr->setOpacity(0.6f); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes + ASSERT_EQ(2U, frame.renderPasses.size()); + + // Even though not enough properties changed, the entire thing must be + // redrawn as we don't have cached textures + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(1U, frame.renderPasses[1]->quadList().size()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_TRUE(targetPass->damageRect().isEmpty()); + + // Was our surface evicted? + EXPECT_FALSE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(targetPass->id())); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Draw without any change, to make sure the state is clear + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive one render pass, as the other one should be culled + ASSERT_EQ(1U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[0]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[0]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_TRUE(targetPass->damageRect().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change opacity on the intermediate layer + WebTransformationMatrix transform = intermediateLayerPtr->transform(); + transform.setM11(1.0001); + intermediateLayerPtr->setTransform(transform); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive one render pass, as the other one should be culled. + ASSERT_EQ(1U, frame.renderPasses.size()); + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[0]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[0]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_TRUE(targetPass->damageRect().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } +} + +TEST_F(CCLayerTreeHostImplTest, surfaceTextureCachingNoPartialSwap) +{ + CCSettings::setPartialSwapEnabled(false); + + CCLayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = IntSize(); + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + CCLayerImpl* rootPtr; + CCLayerImpl* intermediateLayerPtr; + CCLayerImpl* surfaceLayerPtr; + CCLayerImpl* childPtr; + + setupLayersForTextureCaching(myHostImpl.get(), rootPtr, intermediateLayerPtr, surfaceLayerPtr, childPtr, IntSize(100, 100)); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes, each with one quad + ASSERT_EQ(2U, frame.renderPasses.size()); + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(1U, frame.renderPasses[1]->quadList().size()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_FALSE(targetPass->damageRect().isEmpty()); + + EXPECT_FALSE(frame.renderPasses[0]->damageRect().isEmpty()); + EXPECT_FALSE(frame.renderPasses[1]->damageRect().isEmpty()); + + EXPECT_FALSE(frame.renderPasses[0]->hasOcclusionFromOutsideTargetSurface()); + EXPECT_FALSE(frame.renderPasses[1]->hasOcclusionFromOutsideTargetSurface()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Draw without any change + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Even though there was no change, we set the damage to entire viewport. + // One of the passes should be culled as a result, since contents didn't change + // and we have cached texture. + ASSERT_EQ(1U, frame.renderPasses.size()); + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + + EXPECT_TRUE(frame.renderPasses[0]->damageRect().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change opacity and draw + surfaceLayerPtr->setOpacity(0.6f); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive one render pass, as the other one should be culled + ASSERT_EQ(1U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[0]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[0]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_TRUE(targetPass->damageRect().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change less benign property and draw - should have contents changed flag + surfaceLayerPtr->setStackingOrderChanged(true); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes, each with one quad + ASSERT_EQ(2U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(CCDrawQuad::SolidColor, frame.renderPasses[0]->quadList()[0]->material()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_FALSE(targetPass->damageRect().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change opacity again, and evict the cached surface texture. + surfaceLayerPtr->setOpacity(0.5f); + static_cast<CCRendererGLWithReleaseTextures*>(myHostImpl->renderer())->releaseRenderPassTextures(); + + // Change opacity and draw + surfaceLayerPtr->setOpacity(0.6f); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes + ASSERT_EQ(2U, frame.renderPasses.size()); + + // Even though not enough properties changed, the entire thing must be + // redrawn as we don't have cached textures + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(1U, frame.renderPasses[1]->quadList().size()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_TRUE(targetPass->damageRect().isEmpty()); + + // Was our surface evicted? + EXPECT_FALSE(myHostImpl->renderer()->haveCachedResourcesForRenderPassId(targetPass->id())); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Draw without any change, to make sure the state is clear + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Even though there was no change, we set the damage to entire viewport. + // One of the passes should be culled as a result, since contents didn't change + // and we have cached texture. + ASSERT_EQ(1U, frame.renderPasses.size()); + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change opacity on the intermediate layer + WebTransformationMatrix transform = intermediateLayerPtr->transform(); + transform.setM11(1.0001); + intermediateLayerPtr->setTransform(transform); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive one render pass, as the other one should be culled. + ASSERT_EQ(1U, frame.renderPasses.size()); + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[0]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[0]->quadList()[0].get()); + CCRenderPass* targetPass = frame.renderPassesById.get(quad->renderPassId()); + EXPECT_TRUE(targetPass->damageRect().isEmpty()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } +} + +TEST_F(CCLayerTreeHostImplTest, releaseContentsTextureShouldTriggerCommit) +{ + m_hostImpl->releaseContentsTextures(); + EXPECT_TRUE(m_didRequestCommit); +} + +struct RenderPassCacheEntry { + mutable OwnPtr<CCRenderPass> renderPassPtr; + CCRenderPass* renderPass; + + RenderPassCacheEntry(PassOwnPtr<CCRenderPass> r) + : renderPassPtr(r), + renderPass(renderPassPtr.get()) + { + } + + RenderPassCacheEntry() + { + } + + RenderPassCacheEntry(const RenderPassCacheEntry& entry) + : renderPassPtr(entry.renderPassPtr.release()), + renderPass(entry.renderPass) + { + } + + RenderPassCacheEntry& operator=(const RenderPassCacheEntry& entry) + { + renderPassPtr = entry.renderPassPtr.release(); + renderPass = entry.renderPass; + return *this; + } +}; + +struct RenderPassRemovalTestData : public CCLayerTreeHostImpl::FrameData { + std::map<char, RenderPassCacheEntry> renderPassCache; + OwnPtr<CCSharedQuadState> sharedQuadState; +}; + +class CCTestRenderPass: public CCRenderPass { +public: + static PassOwnPtr<CCRenderPass> create(int id, IntRect outputRect, const WebTransformationMatrix& rootTransform) { return adoptPtr(new CCTestRenderPass(id, outputRect, rootTransform)); } + + void appendQuad(PassOwnPtr<CCDrawQuad> quad) { m_quadList.append(quad); } + +protected: + CCTestRenderPass(int id, IntRect outputRect, const WebTransformationMatrix& rootTransform) : CCRenderPass(id, outputRect, rootTransform) { } +}; + +class CCTestRenderer : public CCRendererGL, public CCRendererClient { +public: + static PassOwnPtr<CCTestRenderer> create(CCResourceProvider* resourceProvider) + { + OwnPtr<CCTestRenderer> renderer(adoptPtr(new CCTestRenderer(resourceProvider))); + if (!renderer->initialize()) + return nullptr; + + return renderer.release(); + } + + void clearCachedTextures() { m_textures.clear(); } + void setHaveCachedResourcesForRenderPassId(int id) { m_textures.add(id); } + + virtual bool haveCachedResourcesForRenderPassId(int id) const OVERRIDE { return m_textures.contains(id); } + + // CCRendererClient implementation. + virtual const IntSize& deviceViewportSize() const OVERRIDE { return m_viewportSize; } + virtual const CCLayerTreeSettings& settings() const OVERRIDE { return m_settings; } + virtual void didLoseContext() OVERRIDE { } + virtual void onSwapBuffersComplete() OVERRIDE { } + virtual void setFullRootLayerDamage() OVERRIDE { } + virtual void releaseContentsTextures() OVERRIDE { } + virtual void setMemoryAllocationLimitBytes(size_t) OVERRIDE { } + +protected: + CCTestRenderer(CCResourceProvider* resourceProvider) : CCRendererGL(this, resourceProvider, UnthrottledUploader) { } + +private: + CCLayerTreeSettings m_settings; + IntSize m_viewportSize; + HashSet<int> m_textures; +}; + +static void configureRenderPassTestData(const char* testScript, RenderPassRemovalTestData& testData, CCTestRenderer* renderer) +{ + renderer->clearCachedTextures(); + + // One shared state for all quads - we don't need the correct details + testData.sharedQuadState = CCSharedQuadState::create(WebTransformationMatrix(), IntRect(), IntRect(), 1.0, true); + + const char* currentChar = testScript; + + // Pre-create root pass + char rootRenderPassId = testScript[0]; + OwnPtr<CCRenderPass> rootRenderPass = CCTestRenderPass::create(rootRenderPassId, IntRect(), WebTransformationMatrix()); + testData.renderPassCache.insert(std::pair<char, RenderPassCacheEntry>(rootRenderPassId, RenderPassCacheEntry(rootRenderPass.release()))); + while (*currentChar) { + char renderPassId = currentChar[0]; + currentChar++; + + OwnPtr<CCRenderPass> renderPass; + + bool isReplica = false; + if (!testData.renderPassCache[renderPassId].renderPassPtr.get()) + isReplica = true; + + renderPass = testData.renderPassCache[renderPassId].renderPassPtr.release(); + + // Cycle through quad data and create all quads + while (*currentChar && *currentChar != '\n') { + if (*currentChar == 's') { + // Solid color draw quad + OwnPtr<CCDrawQuad> quad = CCSolidColorDrawQuad::create(testData.sharedQuadState.get(), IntRect(0, 0, 10, 10), SK_ColorWHITE); + + static_cast<CCTestRenderPass*>(renderPass.get())->appendQuad(quad.release()); + currentChar++; + } else if ((*currentChar >= 'A') && (*currentChar <= 'Z')) { + // RenderPass draw quad + char newRenderPassId = *currentChar; + ASSERT_NE(rootRenderPassId, newRenderPassId); + currentChar++; + bool hasTexture = false; + bool contentsChanged = true; + + if (*currentChar == '[') { + currentChar++; + while (*currentChar && *currentChar != ']') { + switch (*currentChar) { + case 'c': + contentsChanged = false; + break; + case 't': + hasTexture = true; + break; + } + currentChar++; + } + if (*currentChar == ']') + currentChar++; + } + + if (testData.renderPassCache.find(newRenderPassId) == testData.renderPassCache.end()) { + if (hasTexture) + renderer->setHaveCachedResourcesForRenderPassId(newRenderPassId); + + OwnPtr<CCRenderPass> renderPass = CCTestRenderPass::create(newRenderPassId, IntRect(), WebTransformationMatrix()); + testData.renderPassCache.insert(std::pair<char, RenderPassCacheEntry>(newRenderPassId, RenderPassCacheEntry(renderPass.release()))); + } + + IntRect quadRect = IntRect(0, 0, 1, 1); + IntRect contentsChangedRect = contentsChanged ? quadRect : IntRect(); + OwnPtr<CCRenderPassDrawQuad> quad = CCRenderPassDrawQuad::create(testData.sharedQuadState.get(), quadRect, newRenderPassId, isReplica, 1, contentsChangedRect, 1, 1, 0, 0); + static_cast<CCTestRenderPass*>(renderPass.get())->appendQuad(quad.release()); + } + } + testData.renderPasses.insert(0, renderPass.get()); + testData.renderPassesById.add(renderPassId, renderPass.release()); + if (*currentChar) + currentChar++; + } +} + +void dumpRenderPassTestData(const RenderPassRemovalTestData& testData, char* buffer) +{ + char* pos = buffer; + for (CCRenderPassList::const_reverse_iterator it = testData.renderPasses.rbegin(); it != testData.renderPasses.rend(); ++it) { + const CCRenderPass* currentPass = *it; + *pos = currentPass->id(); + pos++; + + CCQuadList::const_iterator quadListIterator = currentPass->quadList().begin(); + while (quadListIterator != currentPass->quadList().end()) { + CCDrawQuad* currentQuad = (*quadListIterator).get(); + switch (currentQuad->material()) { + case CCDrawQuad::SolidColor: + *pos = 's'; + pos++; + break; + case CCDrawQuad::RenderPass: + *pos = CCRenderPassDrawQuad::materialCast(currentQuad)->renderPassId(); + pos++; + break; + default: + *pos = 'x'; + pos++; + break; + } + + quadListIterator++; + } + *pos = '\n'; + pos++; + } + *pos = '\0'; +} + +// Each CCRenderPassList is represented by a string which describes the configuration. +// The syntax of the string is as follows: +// +// RsssssX[c]ssYsssZ[t]ssW[ct] +// Identifies the render pass---------------------------^ ^^^ ^ ^ ^ ^ ^ +// These are solid color quads-----------------------------+ | | | | | +// Identifies RenderPassDrawQuad's RenderPass-----------------+ | | | | +// This quad's contents didn't change---------------------------+ | | | +// This quad's contents changed and it has no texture---------------+ | | +// This quad has texture but its contents changed-------------------------+ | +// This quad's contents didn't change and it has texture - will be removed------+ +// +// Expected results have exactly the same syntax, except they do not use square brackets, +// since we only check the structure, not attributes. +// +// Test case configuration consists of initialization script and expected results, +// all in the same format. +struct TestCase { + const char* name; + const char* initScript; + const char* expectedResult; +}; + +TestCase removeRenderPassesCases[] = + { + { + "Single root pass", + "Rssss\n", + "Rssss\n" + }, { + "Single pass - no quads", + "R\n", + "R\n" + }, { + "Two passes, no removal", + "RssssAsss\n" + "Assss\n", + "RssssAsss\n" + "Assss\n" + }, { + "Two passes, remove last", + "RssssA[ct]sss\n" + "Assss\n", + "RssssAsss\n" + }, { + "Have texture but contents changed - leave pass", + "RssssA[t]sss\n" + "Assss\n", + "RssssAsss\n" + "Assss\n" + }, { + "Contents didn't change but no texture - leave pass", + "RssssA[c]sss\n" + "Assss\n", + "RssssAsss\n" + "Assss\n" + }, { + "Replica: two quads reference the same pass; remove", + "RssssA[ct]A[ct]sss\n" + "Assss\n", + "RssssAAsss\n" + }, { + "Replica: two quads reference the same pass; leave", + "RssssA[c]A[c]sss\n" + "Assss\n", + "RssssAAsss\n" + "Assss\n", + }, { + "Many passes, remove all", + "RssssA[ct]sss\n" + "AsssB[ct]C[ct]s\n" + "BsssD[ct]ssE[ct]F[ct]\n" + "Essssss\n" + "CG[ct]\n" + "Dsssssss\n" + "Fsssssss\n" + "Gsss\n", + + "RssssAsss\n" + }, { + "Deep recursion, remove all", + + "RsssssA[ct]ssss\n" + "AssssBsss\n" + "BC\n" + "CD\n" + "DE\n" + "EF\n" + "FG\n" + "GH\n" + "HsssIsss\n" + "IJ\n" + "Jssss\n", + + "RsssssAssss\n" + }, { + "Wide recursion, remove all", + "RA[ct]B[ct]C[ct]D[ct]E[ct]F[ct]G[ct]H[ct]I[ct]J[ct]\n" + "As\n" + "Bs\n" + "Cssss\n" + "Dssss\n" + "Es\n" + "F\n" + "Gs\n" + "Hs\n" + "Is\n" + "Jssss\n", + + "RABCDEFGHIJ\n" + }, { + "Remove passes regardless of cache state", + "RssssA[ct]sss\n" + "AsssBCs\n" + "BsssD[c]ssE[t]F\n" + "Essssss\n" + "CG\n" + "Dsssssss\n" + "Fsssssss\n" + "Gsss\n", + + "RssssAsss\n" + }, { + "Leave some passes, remove others", + + "RssssA[c]sss\n" + "AsssB[t]C[ct]s\n" + "BsssD[c]ss\n" + "CG\n" + "Dsssssss\n" + "Gsss\n", + + "RssssAsss\n" + "AsssBCs\n" + "BsssDss\n" + "Dsssssss\n" + }, { + 0, 0, 0 + } + }; + +static void verifyRenderPassTestData(TestCase& testCase, RenderPassRemovalTestData& testData) +{ + char actualResult[1024]; + dumpRenderPassTestData(testData, actualResult); + EXPECT_STREQ(testCase.expectedResult, actualResult) << "In test case: " << testCase.name; +} + +TEST_F(CCLayerTreeHostImplTest, testRemoveRenderPasses) +{ + OwnPtr<CCGraphicsContext> context(createContext()); + ASSERT_TRUE(context->context3D()); + OwnPtr<CCResourceProvider> resourceProvider(CCResourceProvider::create(context.get())); + + OwnPtr<CCTestRenderer> renderer(CCTestRenderer::create(resourceProvider.get())); + + int testCaseIndex = 0; + while (removeRenderPassesCases[testCaseIndex].name) { + RenderPassRemovalTestData testData; + configureRenderPassTestData(removeRenderPassesCases[testCaseIndex].initScript, testData, renderer.get()); + CCLayerTreeHostImpl::removeRenderPasses(CCLayerTreeHostImpl::CullRenderPassesWithCachedTextures(*renderer), testData); + verifyRenderPassTestData(removeRenderPassesCases[testCaseIndex], testData); + testCaseIndex++; + } +} + +} // namespace diff --git a/cc/CCLayerTreeHostTest.cpp b/cc/CCLayerTreeHostTest.cpp new file mode 100644 index 0000000..820088f --- /dev/null +++ b/cc/CCLayerTreeHostTest.cpp @@ -0,0 +1,2671 @@ +// 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 "config.h" + +#include "CCLayerTreeHost.h" + +#include "CCGraphicsContext.h" +#include "CCLayerTreeHostImpl.h" +#include "CCOcclusionTrackerTestCommon.h" +#include "CCSettings.h" +#include "CCSingleThreadProxy.h" +#include "CCTextureUpdateQueue.h" +#include "CCThreadedTest.h" +#include "CCTimingFunction.h" +#include "ContentLayerChromium.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/Platform.h> +#include <public/WebThread.h> +#include <wtf/MainThread.h> +#include <wtf/OwnArrayPtr.h> + +using namespace WebCore; +using namespace WebKit; +using namespace WebKitTests; +using namespace WTF; + +#define EXPECT_EQ_RECT(a, b) \ + EXPECT_EQ(a.x(), b.x()); \ + EXPECT_EQ(a.y(), b.y()); \ + EXPECT_EQ(a.width(), b.width()); \ + EXPECT_EQ(a.height(), b.height()); + +namespace { + +class CCLayerTreeHostTest : public CCThreadedTest { }; + +// Shortlived layerTreeHosts shouldn't die. +class CCLayerTreeHostTestShortlived1 : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestShortlived1() { } + + virtual void beginTest() OVERRIDE + { + // Kill the layerTreeHost immediately. + m_layerTreeHost->setRootLayer(0); + m_layerTreeHost.clear(); + + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } +}; + +// Shortlived layerTreeHosts shouldn't die with a commit in flight. +class CCLayerTreeHostTestShortlived2 : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestShortlived2() { } + + virtual void beginTest() OVERRIDE + { + postSetNeedsCommitToMainThread(); + + // Kill the layerTreeHost immediately. + m_layerTreeHost->setRootLayer(0); + m_layerTreeHost.clear(); + + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestShortlived2) + +// Shortlived layerTreeHosts shouldn't die with a redraw in flight. +class CCLayerTreeHostTestShortlived3 : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestShortlived3() { } + + virtual void beginTest() OVERRIDE + { + postSetNeedsRedrawToMainThread(); + + // Kill the layerTreeHost immediately. + m_layerTreeHost->setRootLayer(0); + m_layerTreeHost.clear(); + + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestShortlived3) + +// Test interleaving of redraws and commits +class CCLayerTreeHostTestCommitingWithContinuousRedraw : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestCommitingWithContinuousRedraw() + : m_numCompleteCommits(0) + , m_numDraws(0) + { + } + + virtual void beginTest() OVERRIDE + { + postSetNeedsCommitToMainThread(); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + m_numCompleteCommits++; + if (m_numCompleteCommits == 2) + endTest(); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + if (m_numDraws == 1) + postSetNeedsCommitToMainThread(); + m_numDraws++; + postSetNeedsRedrawToMainThread(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: + int m_numCompleteCommits; + int m_numDraws; +}; + +TEST_F(CCLayerTreeHostTestCommitingWithContinuousRedraw, runMultiThread) +{ + runTest(true); +} + +// Two setNeedsCommits in a row should lead to at least 1 commit and at least 1 +// draw with frame 0. +class CCLayerTreeHostTestSetNeedsCommit1 : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestSetNeedsCommit1() + : m_numCommits(0) + , m_numDraws(0) + { + } + + virtual void beginTest() OVERRIDE + { + postSetNeedsCommitToMainThread(); + postSetNeedsCommitToMainThread(); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + m_numDraws++; + if (!impl->sourceFrameNumber()) + endTest(); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + m_numCommits++; + } + + virtual void afterTest() OVERRIDE + { + EXPECT_GE(1, m_numCommits); + EXPECT_GE(1, m_numDraws); + } + +private: + int m_numCommits; + int m_numDraws; +}; + +TEST_F(CCLayerTreeHostTestSetNeedsCommit1, DISABLED_runMultiThread) +{ + runTest(true); +} + +// A setNeedsCommit should lead to 1 commit. Issuing a second commit after that +// first committed frame draws should lead to another commit. +class CCLayerTreeHostTestSetNeedsCommit2 : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestSetNeedsCommit2() + : m_numCommits(0) + , m_numDraws(0) + { + } + + virtual void beginTest() OVERRIDE + { + postSetNeedsCommitToMainThread(); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + if (!impl->sourceFrameNumber()) + postSetNeedsCommitToMainThread(); + else if (impl->sourceFrameNumber() == 1) + endTest(); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + m_numCommits++; + } + + virtual void afterTest() OVERRIDE + { + EXPECT_EQ(2, m_numCommits); + EXPECT_GE(2, m_numDraws); + } + +private: + int m_numCommits; + int m_numDraws; +}; + +#if OS(WINDOWS) +// http://webkit.org/b/74623 +TEST_F(CCLayerTreeHostTestSetNeedsCommit2, FLAKY_runMultiThread) +#else +TEST_F(CCLayerTreeHostTestSetNeedsCommit2, runMultiThread) +#endif +{ + runTest(true); +} + +// 1 setNeedsRedraw after the first commit has completed should lead to 1 +// additional draw. +class CCLayerTreeHostTestSetNeedsRedraw : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestSetNeedsRedraw() + : m_numCommits(0) + , m_numDraws(0) + { + } + + virtual void beginTest() OVERRIDE + { + postSetNeedsCommitToMainThread(); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + EXPECT_EQ(0, impl->sourceFrameNumber()); + if (!m_numDraws) + postSetNeedsRedrawToMainThread(); // Redraw again to verify that the second redraw doesn't commit. + else + endTest(); + m_numDraws++; + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + EXPECT_EQ(0, m_numDraws); + m_numCommits++; + } + + virtual void afterTest() OVERRIDE + { + EXPECT_GE(2, m_numDraws); + EXPECT_EQ(1, m_numCommits); + } + +private: + int m_numCommits; + int m_numDraws; +}; + +TEST_F(CCLayerTreeHostTestSetNeedsRedraw, runMultiThread) +{ + runTest(true); +} + +// If the layerTreeHost says it can't draw, then we should not try to draw. +class CCLayerTreeHostTestCanDrawBlocksDrawing : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestCanDrawBlocksDrawing() + : m_numCommits(0) + { + } + + virtual void beginTest() OVERRIDE + { + postSetNeedsCommitToMainThread(); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + // Only the initial draw should bring us here. + EXPECT_TRUE(impl->canDraw()); + EXPECT_EQ(0, impl->sourceFrameNumber()); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + if (m_numCommits >= 1) { + // After the first commit, we should not be able to draw. + EXPECT_FALSE(impl->canDraw()); + } + } + + virtual void didCommit() OVERRIDE + { + m_numCommits++; + if (m_numCommits == 1) { + // Make the viewport empty so the host says it can't draw. + m_layerTreeHost->setViewportSize(IntSize(0, 0), IntSize(0, 0)); + + OwnArrayPtr<char> pixels(adoptArrayPtr(new char[4])); + m_layerTreeHost->compositeAndReadback(static_cast<void*>(pixels.get()), IntRect(0, 0, 1, 1)); + } else if (m_numCommits == 2) { + m_layerTreeHost->setNeedsRedraw(); + m_layerTreeHost->setNeedsCommit(); + } else + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: + int m_numCommits; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestCanDrawBlocksDrawing) + +// beginLayerWrite should prevent draws from executing until a commit occurs +class CCLayerTreeHostTestWriteLayersRedraw : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestWriteLayersRedraw() + : m_numCommits(0) + , m_numDraws(0) + { + } + + virtual void beginTest() OVERRIDE + { + postAcquireLayerTextures(); + postSetNeedsRedrawToMainThread(); // should be inhibited without blocking + postSetNeedsCommitToMainThread(); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + m_numDraws++; + EXPECT_EQ(m_numDraws, m_numCommits); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + m_numCommits++; + endTest(); + } + + virtual void afterTest() OVERRIDE + { + EXPECT_EQ(1, m_numCommits); + } + +private: + int m_numCommits; + int m_numDraws; +}; + +TEST_F(CCLayerTreeHostTestWriteLayersRedraw, runMultiThread) +{ + runTest(true); +} + +// Verify that when resuming visibility, requesting layer write permission +// will not deadlock the main thread even though there are not yet any +// scheduled redraws. This behavior is critical for reliably surviving tab +// switching. There are no failure conditions to this test, it just passes +// by not timing out. +class CCLayerTreeHostTestWriteLayersAfterVisible : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestWriteLayersAfterVisible() + : m_numCommits(0) + { + } + + virtual void beginTest() OVERRIDE + { + postSetNeedsCommitToMainThread(); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + m_numCommits++; + if (m_numCommits == 2) + endTest(); + else { + postSetVisibleToMainThread(false); + postSetVisibleToMainThread(true); + postAcquireLayerTextures(); + postSetNeedsCommitToMainThread(); + } + } + + virtual void afterTest() OVERRIDE + { + } + +private: + int m_numCommits; +}; + +TEST_F(CCLayerTreeHostTestWriteLayersAfterVisible, runMultiThread) +{ + runTest(true); +} + +// A compositeAndReadback while invisible should force a normal commit without assertion. +class CCLayerTreeHostTestCompositeAndReadbackWhileInvisible : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestCompositeAndReadbackWhileInvisible() + : m_numCommits(0) + { + } + + virtual void beginTest() OVERRIDE + { + } + + virtual void didCommitAndDrawFrame() OVERRIDE + { + m_numCommits++; + if (m_numCommits == 1) { + m_layerTreeHost->setVisible(false); + m_layerTreeHost->setNeedsCommit(); + m_layerTreeHost->setNeedsCommit(); + OwnArrayPtr<char> pixels(adoptArrayPtr(new char[4])); + m_layerTreeHost->compositeAndReadback(static_cast<void*>(pixels.get()), IntRect(0, 0, 1, 1)); + } else + endTest(); + + } + + virtual void afterTest() OVERRIDE + { + } + +private: + int m_numCommits; +}; + +TEST_F(CCLayerTreeHostTestCompositeAndReadbackWhileInvisible, runMultiThread) +{ + runTest(true); +} + +class CCLayerTreeHostTestAbortFrameWhenInvisible : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestAbortFrameWhenInvisible() + { + } + + virtual void beginTest() OVERRIDE + { + // Request a commit (from the main thread), which will trigger the commit flow from the impl side. + m_layerTreeHost->setNeedsCommit(); + // Then mark ourselves as not visible before processing any more messages on the main thread. + m_layerTreeHost->setVisible(false); + // If we make it without kicking a frame, we pass! + endTestAfterDelay(1); + } + + virtual void layout() OVERRIDE + { + ASSERT_FALSE(true); + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: +}; + +TEST_F(CCLayerTreeHostTestAbortFrameWhenInvisible, runMultiThread) +{ + runTest(true); +} + + +// Trigger a frame with setNeedsCommit. Then, inside the resulting animate +// callback, requet another frame using setNeedsAnimate. End the test when +// animate gets called yet-again, indicating that the proxy is correctly +// handling the case where setNeedsAnimate() is called inside the begin frame +// flow. +class CCLayerTreeHostTestSetNeedsAnimateInsideAnimationCallback : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestSetNeedsAnimateInsideAnimationCallback() + : m_numAnimates(0) + { + } + + virtual void beginTest() OVERRIDE + { + postSetNeedsAnimateToMainThread(); + } + + virtual void animate(double) OVERRIDE + { + if (!m_numAnimates) { + m_layerTreeHost->setNeedsAnimate(); + m_numAnimates++; + return; + } + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: + int m_numAnimates; +}; + +TEST_F(CCLayerTreeHostTestSetNeedsAnimateInsideAnimationCallback, runMultiThread) +{ + runTest(true); +} + +// Add a layer animation and confirm that CCLayerTreeHostImpl::animateLayers does get +// called and continues to get called. +class CCLayerTreeHostTestAddAnimation : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestAddAnimation() + : m_numAnimates(0) + , m_receivedAnimationStartedNotification(false) + , m_startTime(0) + , m_firstMonotonicTime(0) + { + } + + virtual void beginTest() OVERRIDE + { + postAddInstantAnimationToMainThread(); + } + + virtual void animateLayers(CCLayerTreeHostImpl* layerTreeHostImpl, double monotonicTime) OVERRIDE + { + if (!m_numAnimates) { + // The animation had zero duration so layerTreeHostImpl should no + // longer need to animate its layers. + EXPECT_FALSE(layerTreeHostImpl->needsAnimateLayers()); + m_numAnimates++; + m_firstMonotonicTime = monotonicTime; + return; + } + EXPECT_LT(0, m_startTime); + EXPECT_LT(0, m_firstMonotonicTime); + EXPECT_NE(m_startTime, m_firstMonotonicTime); + EXPECT_TRUE(m_receivedAnimationStartedNotification); + endTest(); + } + + virtual void notifyAnimationStarted(double wallClockTime) OVERRIDE + { + m_receivedAnimationStartedNotification = true; + m_startTime = wallClockTime; + } + + virtual void afterTest() OVERRIDE + { + } + +private: + int m_numAnimates; + bool m_receivedAnimationStartedNotification; + double m_startTime; + double m_firstMonotonicTime; +}; + +TEST_F(CCLayerTreeHostTestAddAnimation, runMultiThread) +{ + runTest(true); +} + +// Add a layer animation to a layer, but continually fail to draw. Confirm that after +// a while, we do eventually force a draw. +class CCLayerTreeHostTestCheckerboardDoesNotStarveDraws : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestCheckerboardDoesNotStarveDraws() + : m_startedAnimating(false) + { + } + + virtual void beginTest() OVERRIDE + { + postAddAnimationToMainThread(); + } + + virtual void afterTest() OVERRIDE + { + } + + virtual void animateLayers(CCLayerTreeHostImpl* layerTreeHostImpl, double monotonicTime) OVERRIDE + { + m_startedAnimating = true; + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + if (m_startedAnimating) + endTest(); + } + + virtual bool prepareToDrawOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + return false; + } + +private: + bool m_startedAnimating; +}; + +// Starvation can only be an issue with the MT compositor. +TEST_F(CCLayerTreeHostTestCheckerboardDoesNotStarveDraws, runMultiThread) +{ + runTest(true); +} + +// Ensures that animations continue to be ticked when we are backgrounded. +class CCLayerTreeHostTestTickAnimationWhileBackgrounded : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestTickAnimationWhileBackgrounded() + : m_numAnimates(0) + { + } + + virtual void beginTest() OVERRIDE + { + postAddAnimationToMainThread(); + } + + // Use willAnimateLayers to set visible false before the animation runs and + // causes a commit, so we block the second visible animate in single-thread + // mode. + virtual void willAnimateLayers(CCLayerTreeHostImpl* layerTreeHostImpl, double monotonicTime) OVERRIDE + { + if (m_numAnimates < 2) { + if (!m_numAnimates) { + // We have a long animation running. It should continue to tick even if we are not visible. + postSetVisibleToMainThread(false); + } + m_numAnimates++; + return; + } + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: + int m_numAnimates; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestTickAnimationWhileBackgrounded) + +// Ensures that animations continue to be ticked when we are backgrounded. +class CCLayerTreeHostTestAddAnimationWithTimingFunction : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestAddAnimationWithTimingFunction() + { + } + + virtual void beginTest() OVERRIDE + { + postAddAnimationToMainThread(); + } + + virtual void animateLayers(CCLayerTreeHostImpl* layerTreeHostImpl, double monotonicTime) OVERRIDE + { + const CCActiveAnimation* animation = m_layerTreeHost->rootLayer()->layerAnimationController()->getActiveAnimation(0, CCActiveAnimation::Opacity); + if (!animation) + return; + const CCFloatAnimationCurve* curve = animation->curve()->toFloatAnimationCurve(); + float startOpacity = curve->getValue(0); + float endOpacity = curve->getValue(curve->duration()); + float linearlyInterpolatedOpacity = 0.25 * endOpacity + 0.75 * startOpacity; + double time = curve->duration() * 0.25; + // If the linear timing function associated with this animation was not picked up, + // then the linearly interpolated opacity would be different because of the + // default ease timing function. + EXPECT_FLOAT_EQ(linearlyInterpolatedOpacity, curve->getValue(time)); + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestAddAnimationWithTimingFunction) + +// Ensures that when opacity is being animated, this value does not cause the subtree to be skipped. +class CCLayerTreeHostTestDoNotSkipLayersWithAnimatedOpacity : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestDoNotSkipLayersWithAnimatedOpacity() + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->rootLayer()->setDrawOpacity(1); + m_layerTreeHost->setViewportSize(IntSize(10, 10), IntSize(10, 10)); + m_layerTreeHost->rootLayer()->setOpacity(0); + postAddAnimationToMainThread(); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + // If the subtree was skipped when preparing to draw, the layer's draw opacity + // will not have been updated. It should be set to 0 due to the animation. + // Without the animation, the layer will be skipped since it has zero opacity. + EXPECT_EQ(0, m_layerTreeHost->rootLayer()->drawOpacity()); + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } +}; + +#if OS(WINDOWS) +// http://webkit.org/b/74623 +TEST_F(CCLayerTreeHostTestDoNotSkipLayersWithAnimatedOpacity, FLAKY_runMultiThread) +#else +TEST_F(CCLayerTreeHostTestDoNotSkipLayersWithAnimatedOpacity, runMultiThread) +#endif +{ + runTest(true); +} + +// Ensures that main thread animations have their start times synchronized with impl thread animations. +class CCLayerTreeHostTestSynchronizeAnimationStartTimes : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestSynchronizeAnimationStartTimes() + : m_layerTreeHostImpl(0) + { + } + + virtual void beginTest() OVERRIDE + { + postAddAnimationToMainThread(); + } + + // This is guaranteed to be called before CCLayerTreeHostImpl::animateLayers. + virtual void willAnimateLayers(CCLayerTreeHostImpl* layerTreeHostImpl, double monotonicTime) OVERRIDE + { + m_layerTreeHostImpl = layerTreeHostImpl; + } + + virtual void notifyAnimationStarted(double time) OVERRIDE + { + EXPECT_TRUE(m_layerTreeHostImpl); + + CCLayerAnimationController* controllerImpl = m_layerTreeHostImpl->rootLayer()->layerAnimationController(); + CCLayerAnimationController* controller = m_layerTreeHost->rootLayer()->layerAnimationController(); + CCActiveAnimation* animationImpl = controllerImpl->getActiveAnimation(0, CCActiveAnimation::Opacity); + CCActiveAnimation* animation = controller->getActiveAnimation(0, CCActiveAnimation::Opacity); + + EXPECT_EQ(animationImpl->startTime(), animation->startTime()); + + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: + CCLayerTreeHostImpl* m_layerTreeHostImpl; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestSynchronizeAnimationStartTimes) + +// Ensures that main thread animations have their start times synchronized with impl thread animations. +class CCLayerTreeHostTestAnimationFinishedEvents : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestAnimationFinishedEvents() + { + } + + virtual void beginTest() OVERRIDE + { + postAddInstantAnimationToMainThread(); + } + + virtual void notifyAnimationFinished(double time) OVERRIDE + { + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestAnimationFinishedEvents) + +class CCLayerTreeHostTestScrollSimple : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestScrollSimple() + : m_initialScroll(IntPoint(10, 20)) + , m_secondScroll(IntPoint(40, 5)) + , m_scrollAmount(2, -1) + , m_scrolls(0) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->rootLayer()->setScrollable(true); + m_layerTreeHost->rootLayer()->setScrollPosition(m_initialScroll); + postSetNeedsCommitToMainThread(); + } + + virtual void layout() OVERRIDE + { + LayerChromium* root = m_layerTreeHost->rootLayer(); + if (!m_layerTreeHost->commitNumber()) + EXPECT_EQ(root->scrollPosition(), m_initialScroll); + else { + EXPECT_EQ(root->scrollPosition(), m_initialScroll + m_scrollAmount); + + // Pretend like Javascript updated the scroll position itself. + root->setScrollPosition(m_secondScroll); + } + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + CCLayerImpl* root = impl->rootLayer(); + EXPECT_EQ(root->scrollDelta(), IntSize()); + + root->setScrollable(true); + root->setMaxScrollPosition(IntSize(100, 100)); + root->scrollBy(m_scrollAmount); + + if (!impl->sourceFrameNumber()) { + EXPECT_EQ(root->scrollPosition(), m_initialScroll); + EXPECT_EQ(root->scrollDelta(), m_scrollAmount); + postSetNeedsCommitToMainThread(); + } else if (impl->sourceFrameNumber() == 1) { + EXPECT_EQ(root->scrollPosition(), m_secondScroll); + EXPECT_EQ(root->scrollDelta(), m_scrollAmount); + endTest(); + } + } + + virtual void applyScrollAndScale(const IntSize& scrollDelta, float scale) OVERRIDE + { + IntPoint position = m_layerTreeHost->rootLayer()->scrollPosition(); + m_layerTreeHost->rootLayer()->setScrollPosition(position + scrollDelta); + m_scrolls++; + } + + virtual void afterTest() OVERRIDE + { + EXPECT_EQ(1, m_scrolls); + } +private: + IntPoint m_initialScroll; + IntPoint m_secondScroll; + IntSize m_scrollAmount; + int m_scrolls; +}; + +TEST_F(CCLayerTreeHostTestScrollSimple, DISABLED_runMultiThread) +{ + runTest(true); +} + +class CCLayerTreeHostTestScrollMultipleRedraw : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestScrollMultipleRedraw() + : m_initialScroll(IntPoint(40, 10)) + , m_scrollAmount(-3, 17) + , m_scrolls(0) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->rootLayer()->setScrollable(true); + m_layerTreeHost->rootLayer()->setScrollPosition(m_initialScroll); + postSetNeedsCommitToMainThread(); + } + + virtual void beginCommitOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + LayerChromium* root = m_layerTreeHost->rootLayer(); + if (!impl->sourceFrameNumber()) + EXPECT_EQ(root->scrollPosition(), m_initialScroll); + else if (impl->sourceFrameNumber() == 1) + EXPECT_EQ(root->scrollPosition(), m_initialScroll + m_scrollAmount + m_scrollAmount); + else if (impl->sourceFrameNumber() == 2) + EXPECT_EQ(root->scrollPosition(), m_initialScroll + m_scrollAmount + m_scrollAmount); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + CCLayerImpl* root = impl->rootLayer(); + root->setScrollable(true); + root->setMaxScrollPosition(IntSize(100, 100)); + + if (!impl->sourceFrameNumber() && impl->sourceAnimationFrameNumber() == 1) { + // First draw after first commit. + EXPECT_EQ(root->scrollDelta(), IntSize()); + root->scrollBy(m_scrollAmount); + EXPECT_EQ(root->scrollDelta(), m_scrollAmount); + + EXPECT_EQ(root->scrollPosition(), m_initialScroll); + postSetNeedsRedrawToMainThread(); + } else if (!impl->sourceFrameNumber() && impl->sourceAnimationFrameNumber() == 2) { + // Second draw after first commit. + EXPECT_EQ(root->scrollDelta(), m_scrollAmount); + root->scrollBy(m_scrollAmount); + EXPECT_EQ(root->scrollDelta(), m_scrollAmount + m_scrollAmount); + + EXPECT_EQ(root->scrollPosition(), m_initialScroll); + postSetNeedsCommitToMainThread(); + } else if (impl->sourceFrameNumber() == 1) { + // Third or later draw after second commit. + EXPECT_GE(impl->sourceAnimationFrameNumber(), 3); + EXPECT_EQ(root->scrollDelta(), IntSize()); + EXPECT_EQ(root->scrollPosition(), m_initialScroll + m_scrollAmount + m_scrollAmount); + endTest(); + } + } + + virtual void applyScrollAndScale(const IntSize& scrollDelta, float scale) OVERRIDE + { + IntPoint position = m_layerTreeHost->rootLayer()->scrollPosition(); + m_layerTreeHost->rootLayer()->setScrollPosition(position + scrollDelta); + m_scrolls++; + } + + virtual void afterTest() OVERRIDE + { + EXPECT_EQ(1, m_scrolls); + } +private: + IntPoint m_initialScroll; + IntSize m_scrollAmount; + int m_scrolls; +}; + +TEST_F(CCLayerTreeHostTestScrollMultipleRedraw, DISABLED_runMultiThread) +{ + runTest(true); +} + +// This test verifies that properties on the layer tree host are commited to the impl side. +class CCLayerTreeHostTestCommit : public CCLayerTreeHostTest { +public: + + CCLayerTreeHostTestCommit() { } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->setViewportSize(IntSize(20, 20), IntSize(20, 20)); + m_layerTreeHost->setBackgroundColor(SK_ColorGRAY); + m_layerTreeHost->setPageScaleFactorAndLimits(5, 5, 5); + + postSetNeedsCommitToMainThread(); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + EXPECT_EQ(IntSize(20, 20), impl->layoutViewportSize()); + EXPECT_EQ(SK_ColorGRAY, impl->backgroundColor()); + EXPECT_EQ(5, impl->pageScale()); + + endTest(); + } + + virtual void afterTest() OVERRIDE { } +}; + +TEST_F(CCLayerTreeHostTestCommit, runTest) +{ + runTest(true); +} + +// Verifies that startPageScaleAnimation events propagate correctly from CCLayerTreeHost to +// CCLayerTreeHostImpl in the MT compositor. +class CCLayerTreeHostTestStartPageScaleAnimation : public CCLayerTreeHostTest { +public: + + CCLayerTreeHostTestStartPageScaleAnimation() + : m_animationRequested(false) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->rootLayer()->setScrollable(true); + m_layerTreeHost->rootLayer()->setScrollPosition(IntPoint()); + postSetNeedsRedrawToMainThread(); + } + + static void requestStartPageScaleAnimation(void* self) + { + CCLayerTreeHostTestStartPageScaleAnimation* test = static_cast<CCLayerTreeHostTestStartPageScaleAnimation*>(self); + if (test->layerTreeHost()) + test->layerTreeHost()->startPageScaleAnimation(IntSize(), false, 1.25, 0); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + impl->rootLayer()->setScrollable(true); + impl->rootLayer()->setScrollPosition(IntPoint()); + impl->setPageScaleFactorAndLimits(impl->pageScale(), 0.5, 2); + + // We request animation only once. + if (!m_animationRequested) { + callOnMainThread(CCLayerTreeHostTestStartPageScaleAnimation::requestStartPageScaleAnimation, this); + m_animationRequested = true; + } + } + + virtual void applyScrollAndScale(const IntSize& scrollDelta, float scale) OVERRIDE + { + IntPoint position = m_layerTreeHost->rootLayer()->scrollPosition(); + m_layerTreeHost->rootLayer()->setScrollPosition(position + scrollDelta); + m_layerTreeHost->setPageScaleFactorAndLimits(scale, 0.5, 2); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + impl->processScrollDeltas(); + // We get one commit before the first draw, and the animation doesn't happen until the second draw. + if (impl->sourceFrameNumber() == 1) { + EXPECT_EQ(1.25, impl->pageScale()); + endTest(); + } else + postSetNeedsRedrawToMainThread(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: + bool m_animationRequested; +}; + +TEST_F(CCLayerTreeHostTestStartPageScaleAnimation, runTest) +{ + runTest(true); +} + +class CCLayerTreeHostTestSetVisible : public CCLayerTreeHostTest { +public: + + CCLayerTreeHostTestSetVisible() + : m_numDraws(0) + { + } + + virtual void beginTest() OVERRIDE + { + postSetVisibleToMainThread(false); + postSetNeedsRedrawToMainThread(); // This is suppressed while we're invisible. + postSetVisibleToMainThread(true); // Triggers the redraw. + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + EXPECT_TRUE(impl->visible()); + ++m_numDraws; + endTest(); + } + + virtual void afterTest() OVERRIDE + { + EXPECT_EQ(1, m_numDraws); + } + +private: + int m_numDraws; +}; + +TEST_F(CCLayerTreeHostTestSetVisible, runMultiThread) +{ + runTest(true); +} + +class TestOpacityChangeLayerDelegate : public ContentLayerDelegate { +public: + TestOpacityChangeLayerDelegate(CCLayerTreeHostTest* test) + : m_test(test) + { + } + + virtual void paintContents(SkCanvas*, const IntRect&, FloatRect&) OVERRIDE + { + // Set layer opacity to 0. + m_test->layerTreeHost()->rootLayer()->setOpacity(0); + } + +private: + CCLayerTreeHostTest* m_test; +}; + +class ContentLayerChromiumWithUpdateTracking : public ContentLayerChromium { +public: + static PassRefPtr<ContentLayerChromiumWithUpdateTracking> create(ContentLayerDelegate *delegate) { return adoptRef(new ContentLayerChromiumWithUpdateTracking(delegate)); } + + int paintContentsCount() { return m_paintContentsCount; } + void resetPaintContentsCount() { m_paintContentsCount = 0; } + + virtual void update(CCTextureUpdateQueue& queue, const CCOcclusionTracker* occlusion, CCRenderingStats& stats) OVERRIDE + { + ContentLayerChromium::update(queue, occlusion, stats); + m_paintContentsCount++; + } + +private: + explicit ContentLayerChromiumWithUpdateTracking(ContentLayerDelegate* delegate) + : ContentLayerChromium(delegate) + , m_paintContentsCount(0) + { + setAnchorPoint(FloatPoint(0, 0)); + setBounds(IntSize(10, 10)); + setIsDrawable(true); + } + + int m_paintContentsCount; +}; + +// Layer opacity change during paint should not prevent compositor resources from being updated during commit. +class CCLayerTreeHostTestOpacityChange : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestOpacityChange() + : m_testOpacityChangeDelegate(this) + , m_updateCheckLayer(ContentLayerChromiumWithUpdateTracking::create(&m_testOpacityChangeDelegate)) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->setRootLayer(m_updateCheckLayer); + m_layerTreeHost->setViewportSize(IntSize(10, 10), IntSize(10, 10)); + + postSetNeedsCommitToMainThread(); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl*) OVERRIDE + { + endTest(); + } + + virtual void afterTest() OVERRIDE + { + // update() should have been called once. + EXPECT_EQ(1, m_updateCheckLayer->paintContentsCount()); + + // clear m_updateCheckLayer so CCLayerTreeHost dies. + m_updateCheckLayer.clear(); + } + +private: + TestOpacityChangeLayerDelegate m_testOpacityChangeDelegate; + RefPtr<ContentLayerChromiumWithUpdateTracking> m_updateCheckLayer; +}; + +TEST_F(CCLayerTreeHostTestOpacityChange, runMultiThread) +{ + runTest(true); +} + +class MockContentLayerDelegate : public ContentLayerDelegate { +public: + bool drawsContent() const { return true; } + MOCK_CONST_METHOD0(preserves3D, bool()); + void paintContents(SkCanvas*, const IntRect&, FloatRect&) OVERRIDE { } + void notifySyncRequired() { } +}; + +class CCLayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers : public CCLayerTreeHostTest { +public: + + CCLayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers() + : m_rootLayer(ContentLayerChromium::create(&m_delegate)) + , m_childLayer(ContentLayerChromium::create(&m_delegate)) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->setViewportSize(IntSize(40, 40), IntSize(60, 60)); + m_layerTreeHost->setDeviceScaleFactor(1.5); + EXPECT_EQ(IntSize(40, 40), m_layerTreeHost->layoutViewportSize()); + EXPECT_EQ(IntSize(60, 60), m_layerTreeHost->deviceViewportSize()); + + m_rootLayer->addChild(m_childLayer); + + m_rootLayer->setIsDrawable(true); + m_rootLayer->setBounds(IntSize(30, 30)); + m_rootLayer->setAnchorPoint(FloatPoint(0, 0)); + + m_childLayer->setIsDrawable(true); + m_childLayer->setPosition(IntPoint(2, 2)); + m_childLayer->setBounds(IntSize(10, 10)); + m_childLayer->setAnchorPoint(FloatPoint(0, 0)); + + m_layerTreeHost->setRootLayer(m_rootLayer); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + // Get access to protected methods. + MockLayerTreeHostImpl* mockImpl = static_cast<MockLayerTreeHostImpl*>(impl); + + // Should only do one commit. + EXPECT_EQ(0, impl->sourceFrameNumber()); + // Device scale factor should come over to impl. + EXPECT_NEAR(impl->deviceScaleFactor(), 1.5, 0.00001); + + // Both layers are on impl. + ASSERT_EQ(1u, impl->rootLayer()->children().size()); + + // Device viewport is scaled. + EXPECT_EQ(IntSize(40, 40), impl->layoutViewportSize()); + EXPECT_EQ(IntSize(60, 60), impl->deviceViewportSize()); + + CCLayerImpl* root = impl->rootLayer(); + CCLayerImpl* child = impl->rootLayer()->children()[0].get(); + + // Positions remain in layout pixels. + EXPECT_EQ(IntPoint(0, 0), root->position()); + EXPECT_EQ(IntPoint(2, 2), child->position()); + + // Compute all the layer transforms for the frame. + MockLayerTreeHostImpl::CCLayerList renderSurfaceLayerList; + mockImpl->calculateRenderSurfaceLayerList(renderSurfaceLayerList); + + // Both layers should be drawing into the root render surface. + ASSERT_EQ(1u, renderSurfaceLayerList.size()); + ASSERT_EQ(root->renderSurface(), renderSurfaceLayerList[0]->renderSurface()); + ASSERT_EQ(2u, root->renderSurface()->layerList().size()); + + // The root render surface is the size of the viewport. + EXPECT_EQ_RECT(IntRect(0, 0, 60, 60), root->renderSurface()->contentRect()); + + WebTransformationMatrix scaleTransform; + scaleTransform.scale(impl->deviceScaleFactor()); + + // The root layer is scaled by 2x. + WebTransformationMatrix rootScreenSpaceTransform = scaleTransform; + WebTransformationMatrix rootDrawTransform = scaleTransform; + + EXPECT_EQ(rootDrawTransform, root->drawTransform()); + EXPECT_EQ(rootScreenSpaceTransform, root->screenSpaceTransform()); + + // The child is at position 2,2, so translate by 2,2 before applying the scale by 2x. + WebTransformationMatrix childScreenSpaceTransform = scaleTransform; + childScreenSpaceTransform.translate(2, 2); + WebTransformationMatrix childDrawTransform = scaleTransform; + childDrawTransform.translate(2, 2); + + EXPECT_EQ(childDrawTransform, child->drawTransform()); + EXPECT_EQ(childScreenSpaceTransform, child->screenSpaceTransform()); + + endTest(); + } + + virtual void afterTest() OVERRIDE + { + m_rootLayer.clear(); + m_childLayer.clear(); + } + +private: + MockContentLayerDelegate m_delegate; + RefPtr<ContentLayerChromium> m_rootLayer; + RefPtr<ContentLayerChromium> m_childLayer; +}; + +TEST_F(CCLayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers, runMultiThread) +{ + runTest(true); +} + +// Verify atomicity of commits and reuse of textures. +class CCLayerTreeHostTestAtomicCommit : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestAtomicCommit() + : m_layer(ContentLayerChromiumWithUpdateTracking::create(&m_delegate)) + { + // Make sure partial texture updates are turned off. + m_settings.maxPartialTextureUpdates = 0; + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->setRootLayer(m_layer); + m_layerTreeHost->setViewportSize(IntSize(10, 10), IntSize(10, 10)); + + postSetNeedsCommitToMainThread(); + postSetNeedsRedrawToMainThread(); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + CompositorFakeWebGraphicsContext3DWithTextureTracking* context = static_cast<CompositorFakeWebGraphicsContext3DWithTextureTracking*>(impl->context()->context3D()); + + switch (impl->sourceFrameNumber()) { + case 0: + // Number of textures should be one. + ASSERT_EQ(1, context->numTextures()); + // Number of textures used for commit should be one. + EXPECT_EQ(1, context->numUsedTextures()); + // Verify that used texture is correct. + EXPECT_TRUE(context->usedTexture(context->texture(0))); + + context->resetUsedTextures(); + break; + case 1: + // Number of textures should be two as the first texture + // is used by impl thread and cannot by used for update. + ASSERT_EQ(2, context->numTextures()); + // Number of textures used for commit should still be one. + EXPECT_EQ(1, context->numUsedTextures()); + // First texture should not have been used. + EXPECT_FALSE(context->usedTexture(context->texture(0))); + // New texture should have been used. + EXPECT_TRUE(context->usedTexture(context->texture(1))); + + context->resetUsedTextures(); + break; + default: + ASSERT_NOT_REACHED(); + break; + } + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + CompositorFakeWebGraphicsContext3DWithTextureTracking* context = static_cast<CompositorFakeWebGraphicsContext3DWithTextureTracking*>(impl->context()->context3D()); + + // Number of textures used for draw should always be one. + EXPECT_EQ(1, context->numUsedTextures()); + + if (impl->sourceFrameNumber() < 1) { + context->resetUsedTextures(); + postSetNeedsAnimateAndCommitToMainThread(); + postSetNeedsRedrawToMainThread(); + } else + endTest(); + } + + virtual void layout() OVERRIDE + { + m_layer->setNeedsDisplay(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: + MockContentLayerDelegate m_delegate; + RefPtr<ContentLayerChromiumWithUpdateTracking> m_layer; +}; + +TEST_F(CCLayerTreeHostTestAtomicCommit, runMultiThread) +{ + runTest(true); +} + +static void setLayerPropertiesForTesting(LayerChromium* layer, LayerChromium* parent, const WebTransformationMatrix& transform, const FloatPoint& anchor, const FloatPoint& position, const IntSize& bounds, bool opaque) +{ + layer->removeAllChildren(); + if (parent) + parent->addChild(layer); + layer->setTransform(transform); + layer->setAnchorPoint(anchor); + layer->setPosition(position); + layer->setBounds(bounds); + layer->setOpaque(opaque); +} + +class CCLayerTreeHostTestAtomicCommitWithPartialUpdate : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestAtomicCommitWithPartialUpdate() + : m_parent(ContentLayerChromiumWithUpdateTracking::create(&m_delegate)) + , m_child(ContentLayerChromiumWithUpdateTracking::create(&m_delegate)) + , m_numCommits(0) + { + // Allow one partial texture update. + m_settings.maxPartialTextureUpdates = 1; + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->setRootLayer(m_parent); + m_layerTreeHost->setViewportSize(IntSize(10, 20), IntSize(10, 20)); + + WebTransformationMatrix identityMatrix; + setLayerPropertiesForTesting(m_parent.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 20), true); + setLayerPropertiesForTesting(m_child.get(), m_parent.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(0, 10), IntSize(10, 10), false); + + postSetNeedsCommitToMainThread(); + postSetNeedsRedrawToMainThread(); + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + CompositorFakeWebGraphicsContext3DWithTextureTracking* context = static_cast<CompositorFakeWebGraphicsContext3DWithTextureTracking*>(impl->context()->context3D()); + + switch (impl->sourceFrameNumber()) { + case 0: + // Number of textures should be two. + ASSERT_EQ(2, context->numTextures()); + // Number of textures used for commit should be two. + EXPECT_EQ(2, context->numUsedTextures()); + // Verify that used textures are correct. + EXPECT_TRUE(context->usedTexture(context->texture(0))); + EXPECT_TRUE(context->usedTexture(context->texture(1))); + + context->resetUsedTextures(); + break; + case 1: + // Number of textures used for commit should still be two. + EXPECT_EQ(2, context->numUsedTextures()); + // First two textures should not have been used. + EXPECT_FALSE(context->usedTexture(context->texture(0))); + EXPECT_FALSE(context->usedTexture(context->texture(1))); + // New textures should have been used. + EXPECT_TRUE(context->usedTexture(context->texture(2))); + EXPECT_TRUE(context->usedTexture(context->texture(3))); + + context->resetUsedTextures(); + break; + case 2: + // Number of textures used for commit should still be two. + EXPECT_EQ(2, context->numUsedTextures()); + + context->resetUsedTextures(); + break; + case 3: + // No textures should be used for commit. + EXPECT_EQ(0, context->numUsedTextures()); + + context->resetUsedTextures(); + break; + case 4: + // Number of textures used for commit should be one. + EXPECT_EQ(1, context->numUsedTextures()); + + context->resetUsedTextures(); + break; + default: + ASSERT_NOT_REACHED(); + break; + } + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + CompositorFakeWebGraphicsContext3DWithTextureTracking* context = static_cast<CompositorFakeWebGraphicsContext3DWithTextureTracking*>(impl->context()->context3D()); + + // Number of textures used for drawing should two except for frame 4 + // where the viewport only contains one layer. + if (impl->sourceFrameNumber() == 3) + EXPECT_EQ(1, context->numUsedTextures()); + else + EXPECT_EQ(2, context->numUsedTextures()); + + if (impl->sourceFrameNumber() < 4) { + context->resetUsedTextures(); + postSetNeedsAnimateAndCommitToMainThread(); + postSetNeedsRedrawToMainThread(); + } else + endTest(); + } + + virtual void layout() OVERRIDE + { + switch (m_numCommits++) { + case 0: + case 1: + m_parent->setNeedsDisplay(); + m_child->setNeedsDisplay(); + break; + case 2: + // Damage part of layers. + m_parent->setNeedsDisplayRect(FloatRect(0, 0, 5, 5)); + m_child->setNeedsDisplayRect(FloatRect(0, 0, 5, 5)); + break; + case 3: + m_child->setNeedsDisplay(); + m_layerTreeHost->setViewportSize(IntSize(10, 10), IntSize(10, 10)); + break; + case 4: + m_layerTreeHost->setViewportSize(IntSize(10, 20), IntSize(10, 20)); + break; + default: + ASSERT_NOT_REACHED(); + break; + } + } + + virtual void afterTest() OVERRIDE + { + } + +private: + MockContentLayerDelegate m_delegate; + RefPtr<ContentLayerChromiumWithUpdateTracking> m_parent; + RefPtr<ContentLayerChromiumWithUpdateTracking> m_child; + int m_numCommits; +}; + +TEST_F(CCLayerTreeHostTestAtomicCommitWithPartialUpdate, runMultiThread) +{ + runTest(true); +} + +class TestLayerChromium : public LayerChromium { +public: + static PassRefPtr<TestLayerChromium> create() { return adoptRef(new TestLayerChromium()); } + + virtual void update(CCTextureUpdateQueue&, const CCOcclusionTracker* occlusion, CCRenderingStats&) OVERRIDE + { + // Gain access to internals of the CCOcclusionTracker. + const TestCCOcclusionTracker* testOcclusion = static_cast<const TestCCOcclusionTracker*>(occlusion); + m_occludedScreenSpace = testOcclusion ? testOcclusion->occlusionInScreenSpace() : Region(); + } + + virtual bool drawsContent() const OVERRIDE { return true; } + + const Region& occludedScreenSpace() const { return m_occludedScreenSpace; } + void clearOccludedScreenSpace() { m_occludedScreenSpace = Region(); } + +private: + TestLayerChromium() : LayerChromium() { } + + Region m_occludedScreenSpace; +}; + +static void setTestLayerPropertiesForTesting(TestLayerChromium* layer, LayerChromium* parent, const WebTransformationMatrix& transform, const FloatPoint& anchor, const FloatPoint& position, const IntSize& bounds, bool opaque) +{ + setLayerPropertiesForTesting(layer, parent, transform, anchor, position, bounds, opaque); + layer->clearOccludedScreenSpace(); +} + +class CCLayerTreeHostTestLayerOcclusion : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestLayerOcclusion() { } + + virtual void beginTest() OVERRIDE + { + RefPtr<TestLayerChromium> rootLayer = TestLayerChromium::create(); + RefPtr<TestLayerChromium> child = TestLayerChromium::create(); + RefPtr<TestLayerChromium> child2 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> grandChild = TestLayerChromium::create(); + RefPtr<TestLayerChromium> mask = TestLayerChromium::create(); + + WebTransformationMatrix identityMatrix; + WebTransformationMatrix childTransform; + childTransform.translate(250, 250); + childTransform.rotate(90); + childTransform.translate(-250, -250); + + child->setMasksToBounds(true); + + // See CCLayerTreeHostCommonTest.layerAddsSelfToOccludedRegionWithRotatedSurface for a nice visual of these layers and how they end up + // positioned on the screen. + + // The child layer is rotated and the grandChild is opaque, but clipped to the child and rootLayer + setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), false); + setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true); + + m_layerTreeHost->setRootLayer(rootLayer); + m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds()); + ASSERT_TRUE(m_layerTreeHost->initializeRendererIfNeeded()); + CCTextureUpdateQueue queue; + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + EXPECT_EQ_RECT(IntRect(), grandChild->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, grandChild->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 40, 170, 160), child->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, child->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 40, 170, 160), rootLayer->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, rootLayer->occludedScreenSpace().rects().size()); + + // If the child layer is opaque, then it adds to the occlusion seen by the rootLayer. + setLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + setLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true); + setLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true); + + m_layerTreeHost->setRootLayer(rootLayer); + m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds()); + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + EXPECT_EQ_RECT(IntRect(), grandChild->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, grandChild->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 40, 170, 160), child->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, child->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 30, 170, 170), rootLayer->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, rootLayer->occludedScreenSpace().rects().size()); + + // Add a second child to the root layer and the regions should merge + setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(70, 20), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true); + + m_layerTreeHost->setRootLayer(rootLayer); + m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds()); + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + EXPECT_EQ_RECT(IntRect(), grandChild->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, grandChild->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 40, 170, 160), child->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, child->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 30, 170, 170), child2->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, child2->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 20, 170, 180), rootLayer->occludedScreenSpace().bounds()); + EXPECT_EQ(2u, rootLayer->occludedScreenSpace().rects().size()); + + // Move the second child to be sure. + setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 70), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true); + + m_layerTreeHost->setRootLayer(rootLayer); + m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds()); + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + EXPECT_EQ_RECT(IntRect(), grandChild->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, grandChild->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 40, 170, 160), child->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, child->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 30, 170, 170), child2->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, child2->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 30, 190, 170), rootLayer->occludedScreenSpace().bounds()); + EXPECT_EQ(2u, rootLayer->occludedScreenSpace().rects().size()); + + // If the child layer has a mask on it, then it shouldn't contribute to occlusion on stuff below it + setLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + setLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 70), IntSize(500, 500), true); + setLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true); + setLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true); + + child->setMaskLayer(mask.get()); + + m_layerTreeHost->setRootLayer(rootLayer); + m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds()); + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + EXPECT_EQ_RECT(IntRect(), grandChild->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, grandChild->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 40, 170, 160), child->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, child->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(), child2->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, child2->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 70, 190, 130), rootLayer->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, rootLayer->occludedScreenSpace().rects().size()); + + // If the child layer with a mask is below child2, then child2 should contribute to occlusion on everything, and child shouldn't contribute to the rootLayer + setLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + setLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true); + setLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true); + setLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 70), IntSize(500, 500), true); + + child->setMaskLayer(mask.get()); + + m_layerTreeHost->setRootLayer(rootLayer); + m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds()); + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + EXPECT_EQ_RECT(IntRect(), child2->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, child2->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 70, 190, 130), grandChild->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, grandChild->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 40, 190, 160), child->occludedScreenSpace().bounds()); + EXPECT_EQ(2u, child->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 70, 190, 130), rootLayer->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, rootLayer->occludedScreenSpace().rects().size()); + + // If the child layer has a non-opaque drawOpacity, then it shouldn't contribute to occlusion on stuff below it + setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 70), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true); + + child->setMaskLayer(0); + child->setOpacity(0.5); + + m_layerTreeHost->setRootLayer(rootLayer); + m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds()); + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + EXPECT_EQ_RECT(IntRect(), grandChild->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, grandChild->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 40, 170, 160), child->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, child->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(), child2->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, child2->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 70, 190, 130), rootLayer->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, rootLayer->occludedScreenSpace().rects().size()); + + // If the child layer with non-opaque drawOpacity is below child2, then child2 should contribute to occlusion on everything, and child shouldn't contribute to the rootLayer + setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 70), IntSize(500, 500), true); + + child->setMaskLayer(0); + child->setOpacity(0.5); + + m_layerTreeHost->setRootLayer(rootLayer); + m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds()); + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + EXPECT_EQ_RECT(IntRect(), child2->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, child2->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 70, 190, 130), grandChild->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, grandChild->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 40, 190, 160), child->occludedScreenSpace().bounds()); + EXPECT_EQ(2u, child->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 70, 190, 130), rootLayer->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, rootLayer->occludedScreenSpace().rects().size()); + + // Kill the layerTreeHost immediately. + m_layerTreeHost->setRootLayer(0); + m_layerTreeHost.clear(); + + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestLayerOcclusion) + +class CCLayerTreeHostTestLayerOcclusionWithFilters : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestLayerOcclusionWithFilters() { } + + virtual void beginTest() OVERRIDE + { + RefPtr<TestLayerChromium> rootLayer = TestLayerChromium::create(); + RefPtr<TestLayerChromium> child = TestLayerChromium::create(); + RefPtr<TestLayerChromium> child2 = TestLayerChromium::create(); + RefPtr<TestLayerChromium> grandChild = TestLayerChromium::create(); + RefPtr<TestLayerChromium> mask = TestLayerChromium::create(); + + WebTransformationMatrix identityMatrix; + WebTransformationMatrix childTransform; + childTransform.translate(250, 250); + childTransform.rotate(90); + childTransform.translate(-250, -250); + + child->setMasksToBounds(true); + + // If the child layer has a filter that changes alpha values, and is below child2, then child2 should contribute to occlusion on everything, + // and child shouldn't contribute to the rootLayer + setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 70), IntSize(500, 500), true); + + { + WebFilterOperations filters; + filters.append(WebFilterOperation::createOpacityFilter(0.5)); + child->setFilters(filters); + } + + m_layerTreeHost->setRootLayer(rootLayer); + m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds()); + ASSERT_TRUE(m_layerTreeHost->initializeRendererIfNeeded()); + CCTextureUpdateQueue queue; + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + EXPECT_EQ_RECT(IntRect(), child2->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, child2->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 70, 190, 130), grandChild->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, grandChild->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 40, 190, 160), child->occludedScreenSpace().bounds()); + EXPECT_EQ(2u, child->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 70, 190, 130), rootLayer->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, rootLayer->occludedScreenSpace().rects().size()); + + // If the child layer has a filter that moves pixels/changes alpha, and is below child2, then child should not inherit occlusion from outside its subtree, + // and should not contribute to the rootLayer + setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true); + setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, FloatPoint(0, 0), FloatPoint(10, 70), IntSize(500, 500), true); + + { + WebFilterOperations filters; + filters.append(WebFilterOperation::createBlurFilter(10)); + child->setFilters(filters); + } + + m_layerTreeHost->setRootLayer(rootLayer); + m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds()); + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + EXPECT_EQ_RECT(IntRect(), child2->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, child2->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(), grandChild->occludedScreenSpace().bounds()); + EXPECT_EQ(0u, grandChild->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(30, 40, 170, 160), child->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, child->occludedScreenSpace().rects().size()); + EXPECT_EQ_RECT(IntRect(10, 70, 190, 130), rootLayer->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, rootLayer->occludedScreenSpace().rects().size()); + + // Kill the layerTreeHost immediately. + m_layerTreeHost->setRootLayer(0); + m_layerTreeHost.clear(); + + CCLayerTreeHost::setNeedsFilterContext(false); + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestLayerOcclusionWithFilters) + +class CCLayerTreeHostTestManySurfaces : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestManySurfaces() { } + + virtual void beginTest() OVERRIDE + { + // We create enough RenderSurfaces that it will trigger Vector reallocation while computing occlusion. + Region occluded; + const WebTransformationMatrix identityMatrix; + Vector<RefPtr<TestLayerChromium> > layers; + Vector<RefPtr<TestLayerChromium> > children; + int numSurfaces = 20; + RefPtr<TestLayerChromium> replica = TestLayerChromium::create(); + + for (int i = 0; i < numSurfaces; ++i) { + layers.append(TestLayerChromium::create()); + if (!i) { + setTestLayerPropertiesForTesting(layers.last().get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(200, 200), true); + layers.last()->createRenderSurface(); + } else { + setTestLayerPropertiesForTesting(layers.last().get(), layers[layers.size()-2].get(), identityMatrix, FloatPoint(0, 0), FloatPoint(1, 1), IntSize(200-i, 200-i), true); + layers.last()->setMasksToBounds(true); + layers.last()->setReplicaLayer(replica.get()); // Make it have a RenderSurface + } + } + + for (int i = 1; i < numSurfaces; ++i) { + children.append(TestLayerChromium::create()); + setTestLayerPropertiesForTesting(children.last().get(), layers[i].get(), identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(500, 500), false); + } + + m_layerTreeHost->setRootLayer(layers[0].get()); + m_layerTreeHost->setViewportSize(layers[0]->bounds(), layers[0]->bounds()); + ASSERT_TRUE(m_layerTreeHost->initializeRendererIfNeeded()); + CCTextureUpdateQueue queue; + m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max()); + m_layerTreeHost->commitComplete(); + + for (int i = 0; i < numSurfaces-1; ++i) { + IntRect expectedOcclusion(i+1, i+1, 200-i-1, 200-i-1); + + EXPECT_EQ_RECT(expectedOcclusion, layers[i]->occludedScreenSpace().bounds()); + EXPECT_EQ(1u, layers[i]->occludedScreenSpace().rects().size()); + } + + // Kill the layerTreeHost immediately. + m_layerTreeHost->setRootLayer(0); + m_layerTreeHost.clear(); + + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestManySurfaces) + +// A loseContext(1) should lead to a didRecreateOutputSurface(true) +class CCLayerTreeHostTestSetSingleLostContext : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestSetSingleLostContext() + { + } + + virtual void beginTest() OVERRIDE + { + postSetNeedsCommitToMainThread(); + } + + virtual void didCommitAndDrawFrame() OVERRIDE + { + m_layerTreeHost->loseContext(1); + } + + virtual void didRecreateOutputSurface(bool succeeded) OVERRIDE + { + EXPECT_TRUE(succeeded); + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } +}; + +TEST_F(CCLayerTreeHostTestSetSingleLostContext, runMultiThread) +{ + runTest(true); +} + +// A loseContext(10) should lead to a didRecreateOutputSurface(false), and +// a finishAllRendering() should not hang. +class CCLayerTreeHostTestSetRepeatedLostContext : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestSetRepeatedLostContext() + { + } + + virtual void beginTest() OVERRIDE + { + postSetNeedsCommitToMainThread(); + } + + virtual void didCommitAndDrawFrame() OVERRIDE + { + m_layerTreeHost->loseContext(10); + } + + virtual void didRecreateOutputSurface(bool succeeded) OVERRIDE + { + EXPECT_FALSE(succeeded); + m_layerTreeHost->finishAllRendering(); + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } +}; + +TEST_F(CCLayerTreeHostTestSetRepeatedLostContext, runMultiThread) +{ + runTest(true); +} + +class CCLayerTreeHostTestFractionalScroll : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestFractionalScroll() + : m_scrollAmount(1.75, 0) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->rootLayer()->setScrollable(true); + postSetNeedsCommitToMainThread(); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + CCLayerImpl* root = impl->rootLayer(); + root->setMaxScrollPosition(IntSize(100, 100)); + + // Check that a fractional scroll delta is correctly accumulated over multiple commits. + if (!impl->sourceFrameNumber()) { + EXPECT_EQ(root->scrollPosition(), IntPoint(0, 0)); + EXPECT_EQ(root->scrollDelta(), FloatSize(0, 0)); + postSetNeedsCommitToMainThread(); + } else if (impl->sourceFrameNumber() == 1) { + EXPECT_EQ(root->scrollPosition(), flooredIntPoint(m_scrollAmount)); + EXPECT_EQ(root->scrollDelta(), FloatSize(fmod(m_scrollAmount.width(), 1), 0)); + postSetNeedsCommitToMainThread(); + } else if (impl->sourceFrameNumber() == 2) { + EXPECT_EQ(root->scrollPosition(), flooredIntPoint(m_scrollAmount + m_scrollAmount)); + EXPECT_EQ(root->scrollDelta(), FloatSize(fmod(2 * m_scrollAmount.width(), 1), 0)); + endTest(); + } + root->scrollBy(m_scrollAmount); + } + + virtual void applyScrollAndScale(const IntSize& scrollDelta, float scale) OVERRIDE + { + IntPoint position = m_layerTreeHost->rootLayer()->scrollPosition(); + m_layerTreeHost->rootLayer()->setScrollPosition(position + scrollDelta); + } + + virtual void afterTest() OVERRIDE + { + } +private: + FloatSize m_scrollAmount; +}; + +TEST_F(CCLayerTreeHostTestFractionalScroll, runMultiThread) +{ + runTest(true); +} + +class CCLayerTreeHostTestFinishAllRendering : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestFinishAllRendering() + : m_once(false) + , m_mutex() + , m_drawCount(0) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->setNeedsRedraw(); + } + + virtual void didCommitAndDrawFrame() OVERRIDE + { + if (m_once) + return; + m_once = true; + m_layerTreeHost->setNeedsRedraw(); + m_layerTreeHost->acquireLayerTextures(); + { + Locker<Mutex> lock(m_mutex); + m_drawCount = 0; + } + m_layerTreeHost->finishAllRendering(); + { + Locker<Mutex> lock(m_mutex); + EXPECT_EQ(0, m_drawCount); + } + endTest(); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + Locker<Mutex> lock(m_mutex); + ++m_drawCount; + } + + virtual void afterTest() OVERRIDE + { + } +private: + + bool m_once; + Mutex m_mutex; + int m_drawCount; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestFinishAllRendering) + +// Layers added to tree with existing active animations should have the animation +// correctly recognized. +class CCLayerTreeHostTestLayerAddedWithAnimation : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestLayerAddedWithAnimation() + : m_addedAnimation(false) + { + } + + virtual void beginTest() OVERRIDE + { + EXPECT_FALSE(m_addedAnimation); + + RefPtr<LayerChromium> layer = LayerChromium::create(); + layer->setLayerAnimationDelegate(this); + + // Any valid CCAnimationCurve will do here. + OwnPtr<CCAnimationCurve> curve(CCEaseTimingFunction::create()); + OwnPtr<CCActiveAnimation> animation(CCActiveAnimation::create(curve.release(), 1, 1, CCActiveAnimation::Opacity)); + layer->layerAnimationController()->addAnimation(animation.release()); + + // We add the animation *before* attaching the layer to the tree. + m_layerTreeHost->rootLayer()->addChild(layer); + EXPECT_TRUE(m_addedAnimation); + + endTest(); + } + + virtual void didAddAnimation() OVERRIDE + { + m_addedAnimation = true; + } + + virtual void afterTest() OVERRIDE { } + +private: + bool m_addedAnimation; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestLayerAddedWithAnimation) + +class CCLayerTreeHostTestScrollChildLayer : public CCLayerTreeHostTest, public LayerChromiumScrollDelegate { +public: + CCLayerTreeHostTestScrollChildLayer() + : m_scrollAmount(2, 1) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->setViewportSize(IntSize(10, 10), IntSize(10, 10)); + m_layerTreeHost->rootLayer()->setBounds(IntSize(10, 10)); + + m_rootScrollLayer = ContentLayerChromium::create(&m_mockDelegate); + m_rootScrollLayer->setBounds(IntSize(10, 10)); + + m_rootScrollLayer->setPosition(FloatPoint(0, 0)); + m_rootScrollLayer->setAnchorPoint(FloatPoint(0, 0)); + + m_rootScrollLayer->setIsDrawable(true); + m_rootScrollLayer->setScrollable(true); + m_rootScrollLayer->setMaxScrollPosition(IntSize(100, 100)); + m_layerTreeHost->rootLayer()->addChild(m_rootScrollLayer); + m_childLayer = ContentLayerChromium::create(&m_mockDelegate); + m_childLayer->setLayerScrollDelegate(this); + m_childLayer->setBounds(IntSize(50, 50)); + m_childLayer->setIsDrawable(true); + m_childLayer->setScrollable(true); + m_childLayer->setMaxScrollPosition(IntSize(100, 100)); + + m_childLayer->setPosition(FloatPoint(0, 0)); + m_childLayer->setAnchorPoint(FloatPoint(0, 0)); + + m_rootScrollLayer->addChild(m_childLayer); + postSetNeedsCommitToMainThread(); + } + + virtual void didScroll(const IntSize& scrollDelta) OVERRIDE + { + m_reportedScrollAmount = scrollDelta; + } + + virtual void applyScrollAndScale(const IntSize& scrollDelta, float) OVERRIDE + { + IntPoint position = m_rootScrollLayer->scrollPosition(); + m_rootScrollLayer->setScrollPosition(position + scrollDelta); + } + + virtual void beginCommitOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + EXPECT_EQ(m_rootScrollLayer->scrollPosition(), IntPoint()); + if (!m_layerTreeHost->commitNumber()) + EXPECT_EQ(m_childLayer->scrollPosition(), IntPoint()); + else + EXPECT_EQ(m_childLayer->scrollPosition(), IntPoint() + m_scrollAmount); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + if (impl->sourceAnimationFrameNumber() == 1) { + EXPECT_EQ(impl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + impl->scrollBy(IntPoint(), m_scrollAmount); + impl->scrollEnd(); + } else if (impl->sourceAnimationFrameNumber() == 2) + endTest(); + } + + virtual void afterTest() OVERRIDE + { + EXPECT_EQ(m_scrollAmount, m_reportedScrollAmount); + } + +private: + const IntSize m_scrollAmount; + IntSize m_reportedScrollAmount; + MockContentLayerDelegate m_mockDelegate; + RefPtr<LayerChromium> m_childLayer; + RefPtr<LayerChromium> m_rootScrollLayer; +}; + +TEST_F(CCLayerTreeHostTestScrollChildLayer, runMultiThread) +{ + runTest(true); +} + +class CCLayerTreeHostTestCompositeAndReadbackCleanup : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestCompositeAndReadbackCleanup() { } + + virtual void beginTest() OVERRIDE + { + LayerChromium* rootLayer = m_layerTreeHost->rootLayer(); + + OwnArrayPtr<char> pixels(adoptArrayPtr(new char[4])); + m_layerTreeHost->compositeAndReadback(static_cast<void*>(pixels.get()), IntRect(0, 0, 1, 1)); + EXPECT_FALSE(rootLayer->renderSurface()); + + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestCompositeAndReadbackCleanup) + +class CCLayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit() + : m_rootLayer(ContentLayerChromiumWithUpdateTracking::create(&m_mockDelegate)) + , m_surfaceLayer1(ContentLayerChromiumWithUpdateTracking::create(&m_mockDelegate)) + , m_replicaLayer1(ContentLayerChromiumWithUpdateTracking::create(&m_mockDelegate)) + , m_surfaceLayer2(ContentLayerChromiumWithUpdateTracking::create(&m_mockDelegate)) + , m_replicaLayer2(ContentLayerChromiumWithUpdateTracking::create(&m_mockDelegate)) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->setViewportSize(IntSize(100, 100), IntSize(100, 100)); + + m_rootLayer->setBounds(IntSize(100, 100)); + m_surfaceLayer1->setBounds(IntSize(100, 100)); + m_surfaceLayer1->setForceRenderSurface(true); + m_surfaceLayer1->setOpacity(0.5); + m_surfaceLayer2->setBounds(IntSize(100, 100)); + m_surfaceLayer2->setForceRenderSurface(true); + m_surfaceLayer2->setOpacity(0.5); + + m_surfaceLayer1->setReplicaLayer(m_replicaLayer1.get()); + m_surfaceLayer2->setReplicaLayer(m_replicaLayer2.get()); + + m_rootLayer->addChild(m_surfaceLayer1); + m_surfaceLayer1->addChild(m_surfaceLayer2); + m_layerTreeHost->setRootLayer(m_rootLayer); + } + + virtual void drawLayersOnCCThread(CCLayerTreeHostImpl* hostImpl) OVERRIDE + { + CCRenderer* renderer = hostImpl->renderer(); + unsigned surface1RenderPassId = hostImpl->rootLayer()->children()[0]->id(); + unsigned surface2RenderPassId = hostImpl->rootLayer()->children()[0]->children()[0]->id(); + + switch (hostImpl->sourceFrameNumber()) { + case 0: + EXPECT_TRUE(renderer->haveCachedResourcesForRenderPassId(surface1RenderPassId)); + EXPECT_TRUE(renderer->haveCachedResourcesForRenderPassId(surface2RenderPassId)); + + // Reduce the memory limit to only fit the root layer and one render surface. This + // prevents any contents drawing into surfaces from being allocated. + hostImpl->setMemoryAllocationLimitBytes(100 * 100 * 4 * 2); + break; + case 1: + EXPECT_FALSE(renderer->haveCachedResourcesForRenderPassId(surface1RenderPassId)); + EXPECT_FALSE(renderer->haveCachedResourcesForRenderPassId(surface2RenderPassId)); + + endTest(); + break; + } + } + + virtual void afterTest() OVERRIDE + { + EXPECT_EQ(2, m_rootLayer->paintContentsCount()); + EXPECT_EQ(2, m_surfaceLayer1->paintContentsCount()); + EXPECT_EQ(2, m_surfaceLayer2->paintContentsCount()); + + // Clear layer references so CCLayerTreeHost dies. + m_rootLayer.clear(); + m_surfaceLayer1.clear(); + m_replicaLayer1.clear(); + m_surfaceLayer2.clear(); + m_replicaLayer2.clear(); + } + +private: + MockContentLayerDelegate m_mockDelegate; + RefPtr<ContentLayerChromiumWithUpdateTracking> m_rootLayer; + RefPtr<ContentLayerChromiumWithUpdateTracking> m_surfaceLayer1; + RefPtr<ContentLayerChromiumWithUpdateTracking> m_replicaLayer1; + RefPtr<ContentLayerChromiumWithUpdateTracking> m_surfaceLayer2; + RefPtr<ContentLayerChromiumWithUpdateTracking> m_replicaLayer2; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit) + + +class EvictionTrackingTexture : public LayerTextureUpdater::Texture { +public: + static PassOwnPtr<EvictionTrackingTexture> create(PassOwnPtr<CCPrioritizedTexture> texture) { return adoptPtr(new EvictionTrackingTexture(texture)); } + virtual ~EvictionTrackingTexture() { } + + virtual void updateRect(CCResourceProvider* resourceProvider, const IntRect&, const IntSize&) OVERRIDE + { + ASSERT_TRUE(!texture()->haveBackingTexture() || resourceProvider->numResources() > 0); + texture()->acquireBackingTexture(resourceProvider); + m_updated = true; + } + void resetUpdated() { m_updated = false; } + bool updated() const { return m_updated; } + +private: + explicit EvictionTrackingTexture(PassOwnPtr<CCPrioritizedTexture> texture) + : LayerTextureUpdater::Texture(texture) + , m_updated(false) + { } + bool m_updated; +}; + +class EvictionTestLayer : public LayerChromium { +public: + static PassRefPtr<EvictionTestLayer> create() { return adoptRef(new EvictionTestLayer()); } + + virtual void update(CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&) OVERRIDE; + virtual bool drawsContent() const OVERRIDE { return true; } + + virtual PassOwnPtr<CCLayerImpl> createCCLayerImpl() OVERRIDE; + virtual void pushPropertiesTo(CCLayerImpl*) OVERRIDE; + virtual void setTexturePriorities(const CCPriorityCalculator&) OVERRIDE; + + void resetUpdated() + { + if (m_texture.get()) + m_texture->resetUpdated(); + } + bool updated() const { return m_texture.get() ? m_texture->updated() : false; } + +private: + EvictionTestLayer() : LayerChromium() { } + + void createTextureIfNeeded() + { + if (m_texture.get()) + return; + m_texture = EvictionTrackingTexture::create(CCPrioritizedTexture::create(layerTreeHost()->contentsTextureManager())); + m_texture->texture()->setDimensions(WebCore::IntSize(10, 10), WebCore::GraphicsContext3D::RGBA); + } + + OwnPtr<EvictionTrackingTexture> m_texture; +}; + +class EvictionTestLayerImpl : public CCLayerImpl { +public: + static PassOwnPtr<EvictionTestLayerImpl> create(int id) + { + return adoptPtr(new EvictionTestLayerImpl(id)); + } + virtual ~EvictionTestLayerImpl() { } + + virtual void appendQuads(CCQuadSink&, bool& hadMissingTiles) OVERRIDE + { + ASSERT_TRUE(m_hasTexture); + ASSERT_NE(0u, layerTreeHostImpl()->resourceProvider()->numResources()); + } + + void setHasTexture(bool hasTexture) { m_hasTexture = hasTexture; } + +private: + explicit EvictionTestLayerImpl(int id) + : CCLayerImpl(id) + , m_hasTexture(false) { } + + bool m_hasTexture; +}; + +void EvictionTestLayer::setTexturePriorities(const CCPriorityCalculator&) +{ + createTextureIfNeeded(); + if (!m_texture.get()) + return; + m_texture->texture()->setRequestPriority(CCPriorityCalculator::uiPriority(true)); +} + +void EvictionTestLayer::update(CCTextureUpdateQueue& queue, const CCOcclusionTracker*, CCRenderingStats&) +{ + createTextureIfNeeded(); + if (!m_texture.get()) + return; + IntRect fullRect(0, 0, 10, 10); + TextureUploader::Parameters parameters = { m_texture.get(), fullRect, IntSize() }; + queue.appendFullUpload(parameters); +} + +PassOwnPtr<CCLayerImpl> EvictionTestLayer::createCCLayerImpl() +{ + return EvictionTestLayerImpl::create(m_layerId); +} + +void EvictionTestLayer::pushPropertiesTo(CCLayerImpl* layerImpl) +{ + LayerChromium::pushPropertiesTo(layerImpl); + + EvictionTestLayerImpl* testLayerImpl = static_cast<EvictionTestLayerImpl*>(layerImpl); + testLayerImpl->setHasTexture(m_texture->texture()->haveBackingTexture()); +} + +class CCLayerTreeHostTestEvictTextures : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestEvictTextures() + : m_layer(EvictionTestLayer::create()) + , m_implForEvictTextures(0) + , m_numCommits(0) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->setRootLayer(m_layer); + m_layerTreeHost->setViewportSize(IntSize(10, 20), IntSize(10, 20)); + + WebTransformationMatrix identityMatrix; + setLayerPropertiesForTesting(m_layer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 20), true); + } + + class EvictTexturesTask : public WebKit::WebThread::Task { + public: + EvictTexturesTask(CCLayerTreeHostTestEvictTextures* test) : m_test(test) { } + virtual ~EvictTexturesTask() { } + virtual void run() OVERRIDE + { + ASSERT(m_test->m_implForEvictTextures); + m_test->m_implForEvictTextures->releaseContentsTextures(); + } + + private: + CCLayerTreeHostTestEvictTextures* m_test; + }; + + void postEvictTextures() + { + ASSERT(webThread()); + webThread()->postTask(new EvictTexturesTask(this)); + } + + // Commit 1: Just commit and draw normally, then post an eviction at the end + // that will trigger a commit. + // Commit 2: Triggered by the eviction, let it go through and then set + // needsCommit. + // Commit 3: Triggered by the setNeedsCommit. In layout(), post an eviction + // task, which will be handled before the commit. Don't set needsCommit, it + // should have been posted. A frame should not be drawn (note, + // didCommitAndDrawFrame may be called anyway). + // Commit 4: Triggered by the eviction, let it go through and then set + // needsCommit. + // Commit 5: Triggered by the setNeedsCommit, post an eviction task in + // layout(), a frame should not be drawn but a commit will be posted. + // Commit 6: Triggered by the eviction, post an eviction task in + // layout(), which will be a noop, letting the commit (which recreates the + // textures) go through and draw a frame, then end the test. + // + // Commits 1+2 test the eviction recovery path where eviction happens outside + // of the beginFrame/commit pair. + // Commits 3+4 test the eviction recovery path where eviction happens inside + // the beginFrame/commit pair. + // Commits 5+6 test the path where an eviction happens during the eviction + // recovery path. + virtual void didCommitAndDrawFrame() OVERRIDE + { + switch (m_numCommits) { + case 1: + EXPECT_TRUE(m_layer->updated()); + postEvictTextures(); + break; + case 2: + EXPECT_TRUE(m_layer->updated()); + m_layerTreeHost->setNeedsCommit(); + break; + case 3: + break; + case 4: + EXPECT_TRUE(m_layer->updated()); + m_layerTreeHost->setNeedsCommit(); + break; + case 5: + break; + case 6: + EXPECT_TRUE(m_layer->updated()); + endTest(); + break; + default: + ASSERT_NOT_REACHED(); + break; + } + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + m_implForEvictTextures = impl; + } + + virtual void layout() OVERRIDE + { + ++m_numCommits; + switch (m_numCommits) { + case 1: + case 2: + break; + case 3: + postEvictTextures(); + break; + case 4: + // We couldn't check in didCommitAndDrawFrame on commit 3, so check here. + EXPECT_FALSE(m_layer->updated()); + break; + case 5: + postEvictTextures(); + break; + case 6: + // We couldn't check in didCommitAndDrawFrame on commit 5, so check here. + EXPECT_FALSE(m_layer->updated()); + postEvictTextures(); + break; + default: + ASSERT_NOT_REACHED(); + break; + } + m_layer->resetUpdated(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: + MockContentLayerDelegate m_delegate; + RefPtr<EvictionTestLayer> m_layer; + CCLayerTreeHostImpl* m_implForEvictTextures; + int m_numCommits; +}; + +TEST_F(CCLayerTreeHostTestEvictTextures, runMultiThread) +{ + runTest(true); +} + +class CCLayerTreeHostTestLostContextAfterEvictTextures : public CCLayerTreeHostTest { +public: + CCLayerTreeHostTestLostContextAfterEvictTextures() + : m_layer(EvictionTestLayer::create()) + , m_implForEvictTextures(0) + , m_numCommits(0) + { + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->setRootLayer(m_layer); + m_layerTreeHost->setViewportSize(IntSize(10, 20), IntSize(10, 20)); + + WebTransformationMatrix identityMatrix; + setLayerPropertiesForTesting(m_layer.get(), 0, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(10, 20), true); + } + + class EvictTexturesTask : public WebKit::WebThread::Task { + public: + EvictTexturesTask(CCLayerTreeHostTestLostContextAfterEvictTextures* test) : m_test(test) { } + virtual ~EvictTexturesTask() { } + virtual void run() OVERRIDE + { + m_test->evictTexturesOnImplThread(); + } + + private: + CCLayerTreeHostTestLostContextAfterEvictTextures* m_test; + }; + + void postEvictTextures() + { + if (webThread()) + webThread()->postTask(new EvictTexturesTask(this)); + else { + DebugScopedSetImplThread impl; + evictTexturesOnImplThread(); + } + } + + void evictTexturesOnImplThread() + { + ASSERT(m_implForEvictTextures); + m_implForEvictTextures->releaseContentsTextures(); + } + + // Commit 1: Just commit and draw normally, then at the end, set ourselves + // invisible (to prevent a commit that would recreate textures after + // eviction, before the context recovery), and post a task that will evict + // textures, then cause the context to be lost, and then set ourselves + // visible again (to allow commits, since that's what causes context + // recovery in single thread). + virtual void didCommitAndDrawFrame() OVERRIDE + { + ++m_numCommits; + switch (m_numCommits) { + case 1: + EXPECT_TRUE(m_layer->updated()); + m_layerTreeHost->setVisible(false); + postEvictTextures(); + m_layerTreeHost->loseContext(1); + m_layerTreeHost->setVisible(true); + break; + default: + break; + } + } + + virtual void commitCompleteOnCCThread(CCLayerTreeHostImpl* impl) OVERRIDE + { + m_implForEvictTextures = impl; + } + + virtual void didRecreateOutputSurface(bool succeeded) OVERRIDE + { + EXPECT_TRUE(succeeded); + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: + MockContentLayerDelegate m_delegate; + RefPtr<EvictionTestLayer> m_layer; + CCLayerTreeHostImpl* m_implForEvictTextures; + int m_numCommits; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(CCLayerTreeHostTestLostContextAfterEvictTextures) + +} // namespace diff --git a/cc/CCMathUtil.cpp b/cc/CCMathUtil.cpp new file mode 100644 index 0000000..c8969a0 --- /dev/null +++ b/cc/CCMathUtil.cpp @@ -0,0 +1,379 @@ +// Copyright 2012 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 "CCMathUtil.h" + +#include "FloatPoint.h" +#include "FloatQuad.h" +#include "IntRect.h" +#include <public/WebTransformationMatrix.h> + +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +static HomogeneousCoordinate projectHomogeneousPoint(const WebTransformationMatrix& transform, const FloatPoint& p) +{ + // In this case, the layer we are trying to project onto is perpendicular to ray + // (point p and z-axis direction) that we are trying to project. This happens when the + // layer is rotated so that it is infinitesimally thin, or when it is co-planar with + // the camera origin -- i.e. when the layer is invisible anyway. + if (!transform.m33()) + return HomogeneousCoordinate(0, 0, 0, 1); + + double x = p.x(); + double y = p.y(); + double z = -(transform.m13() * x + transform.m23() * y + transform.m43()) / transform.m33(); + // implicit definition of w = 1; + + double outX = x * transform.m11() + y * transform.m21() + z * transform.m31() + transform.m41(); + double outY = x * transform.m12() + y * transform.m22() + z * transform.m32() + transform.m42(); + double outZ = x * transform.m13() + y * transform.m23() + z * transform.m33() + transform.m43(); + double outW = x * transform.m14() + y * transform.m24() + z * transform.m34() + transform.m44(); + + return HomogeneousCoordinate(outX, outY, outZ, outW); +} + +static HomogeneousCoordinate mapHomogeneousPoint(const WebTransformationMatrix& transform, const FloatPoint3D& p) +{ + double x = p.x(); + double y = p.y(); + double z = p.z(); + // implicit definition of w = 1; + + double outX = x * transform.m11() + y * transform.m21() + z * transform.m31() + transform.m41(); + double outY = x * transform.m12() + y * transform.m22() + z * transform.m32() + transform.m42(); + double outZ = x * transform.m13() + y * transform.m23() + z * transform.m33() + transform.m43(); + double outW = x * transform.m14() + y * transform.m24() + z * transform.m34() + transform.m44(); + + return HomogeneousCoordinate(outX, outY, outZ, outW); +} + +static HomogeneousCoordinate computeClippedPointForEdge(const HomogeneousCoordinate& h1, const HomogeneousCoordinate& h2) +{ + // Points h1 and h2 form a line in 4d, and any point on that line can be represented + // as an interpolation between h1 and h2: + // p = (1-t) h1 + (t) h2 + // + // We want to compute point p such that p.w == epsilon, where epsilon is a small + // non-zero number. (but the smaller the number is, the higher the risk of overflow) + // To do this, we solve for t in the following equation: + // p.w = epsilon = (1-t) * h1.w + (t) * h2.w + // + // Once paramter t is known, the rest of p can be computed via p = (1-t) h1 + (t) h2. + + // Technically this is a special case of the following assertion, but its a good idea to keep it an explicit sanity check here. + ASSERT(h2.w != h1.w); + // Exactly one of h1 or h2 (but not both) must be on the negative side of the w plane when this is called. + ASSERT(h1.shouldBeClipped() ^ h2.shouldBeClipped()); + + double w = 0.00001; // or any positive non-zero small epsilon + + double t = (w - h1.w) / (h2.w - h1.w); + + double x = (1-t) * h1.x + t * h2.x; + double y = (1-t) * h1.y + t * h2.y; + double z = (1-t) * h1.z + t * h2.z; + + return HomogeneousCoordinate(x, y, z, w); +} + +static inline void expandBoundsToIncludePoint(float& xmin, float& xmax, float& ymin, float& ymax, const FloatPoint& p) +{ + xmin = std::min(p.x(), xmin); + xmax = std::max(p.x(), xmax); + ymin = std::min(p.y(), ymin); + ymax = std::max(p.y(), ymax); +} + +static inline void addVertexToClippedQuad(const FloatPoint& newVertex, FloatPoint clippedQuad[8], int& numVerticesInClippedQuad) +{ + clippedQuad[numVerticesInClippedQuad] = newVertex; + numVerticesInClippedQuad++; +} + +IntRect CCMathUtil::mapClippedRect(const WebTransformationMatrix& transform, const IntRect& srcRect) +{ + return enclosingIntRect(mapClippedRect(transform, FloatRect(srcRect))); +} + +FloatRect CCMathUtil::mapClippedRect(const WebTransformationMatrix& transform, const FloatRect& srcRect) +{ + if (transform.isIdentityOrTranslation()) { + FloatRect mappedRect(srcRect); + mappedRect.move(static_cast<float>(transform.m41()), static_cast<float>(transform.m42())); + return mappedRect; + } + + // Apply the transform, but retain the result in homogeneous coordinates. + FloatQuad q = FloatQuad(FloatRect(srcRect)); + HomogeneousCoordinate h1 = mapHomogeneousPoint(transform, q.p1()); + HomogeneousCoordinate h2 = mapHomogeneousPoint(transform, q.p2()); + HomogeneousCoordinate h3 = mapHomogeneousPoint(transform, q.p3()); + HomogeneousCoordinate h4 = mapHomogeneousPoint(transform, q.p4()); + + return computeEnclosingClippedRect(h1, h2, h3, h4); +} + +FloatRect CCMathUtil::projectClippedRect(const WebTransformationMatrix& transform, const FloatRect& srcRect) +{ + // Perform the projection, but retain the result in homogeneous coordinates. + FloatQuad q = FloatQuad(FloatRect(srcRect)); + HomogeneousCoordinate h1 = projectHomogeneousPoint(transform, q.p1()); + HomogeneousCoordinate h2 = projectHomogeneousPoint(transform, q.p2()); + HomogeneousCoordinate h3 = projectHomogeneousPoint(transform, q.p3()); + HomogeneousCoordinate h4 = projectHomogeneousPoint(transform, q.p4()); + + return computeEnclosingClippedRect(h1, h2, h3, h4); +} + +void CCMathUtil::mapClippedQuad(const WebTransformationMatrix& transform, const FloatQuad& srcQuad, FloatPoint clippedQuad[8], int& numVerticesInClippedQuad) +{ + HomogeneousCoordinate h1 = mapHomogeneousPoint(transform, srcQuad.p1()); + HomogeneousCoordinate h2 = mapHomogeneousPoint(transform, srcQuad.p2()); + HomogeneousCoordinate h3 = mapHomogeneousPoint(transform, srcQuad.p3()); + HomogeneousCoordinate h4 = mapHomogeneousPoint(transform, srcQuad.p4()); + + // The order of adding the vertices to the array is chosen so that clockwise / counter-clockwise orientation is retained. + + numVerticesInClippedQuad = 0; + + if (!h1.shouldBeClipped()) + addVertexToClippedQuad(h1.cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad); + + if (h1.shouldBeClipped() ^ h2.shouldBeClipped()) + addVertexToClippedQuad(computeClippedPointForEdge(h1, h2).cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad); + + if (!h2.shouldBeClipped()) + addVertexToClippedQuad(h2.cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad); + + if (h2.shouldBeClipped() ^ h3.shouldBeClipped()) + addVertexToClippedQuad(computeClippedPointForEdge(h2, h3).cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad); + + if (!h3.shouldBeClipped()) + addVertexToClippedQuad(h3.cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad); + + if (h3.shouldBeClipped() ^ h4.shouldBeClipped()) + addVertexToClippedQuad(computeClippedPointForEdge(h3, h4).cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad); + + if (!h4.shouldBeClipped()) + addVertexToClippedQuad(h4.cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad); + + if (h4.shouldBeClipped() ^ h1.shouldBeClipped()) + addVertexToClippedQuad(computeClippedPointForEdge(h4, h1).cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad); + + ASSERT(numVerticesInClippedQuad <= 8); +} + +FloatRect CCMathUtil::computeEnclosingRectOfVertices(FloatPoint vertices[], int numVertices) +{ + if (numVertices < 2) + return FloatRect(); + + float xmin = std::numeric_limits<float>::max(); + float xmax = -std::numeric_limits<float>::max(); + float ymin = std::numeric_limits<float>::max(); + float ymax = -std::numeric_limits<float>::max(); + + for (int i = 0; i < numVertices; ++i) + expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, vertices[i]); + + return FloatRect(FloatPoint(xmin, ymin), FloatSize(xmax - xmin, ymax - ymin)); +} + +FloatRect CCMathUtil::computeEnclosingClippedRect(const HomogeneousCoordinate& h1, const HomogeneousCoordinate& h2, const HomogeneousCoordinate& h3, const HomogeneousCoordinate& h4) +{ + // This function performs clipping as necessary and computes the enclosing 2d + // FloatRect of the vertices. Doing these two steps simultaneously allows us to avoid + // the overhead of storing an unknown number of clipped vertices. + + // If no vertices on the quad are clipped, then we can simply return the enclosing rect directly. + bool somethingClipped = h1.shouldBeClipped() || h2.shouldBeClipped() || h3.shouldBeClipped() || h4.shouldBeClipped(); + if (!somethingClipped) { + FloatQuad mappedQuad = FloatQuad(h1.cartesianPoint2d(), h2.cartesianPoint2d(), h3.cartesianPoint2d(), h4.cartesianPoint2d()); + return mappedQuad.boundingBox(); + } + + bool everythingClipped = h1.shouldBeClipped() && h2.shouldBeClipped() && h3.shouldBeClipped() && h4.shouldBeClipped(); + if (everythingClipped) + return FloatRect(); + + + float xmin = std::numeric_limits<float>::max(); + float xmax = -std::numeric_limits<float>::max(); + float ymin = std::numeric_limits<float>::max(); + float ymax = -std::numeric_limits<float>::max(); + + if (!h1.shouldBeClipped()) + expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h1.cartesianPoint2d()); + + if (h1.shouldBeClipped() ^ h2.shouldBeClipped()) + expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h1, h2).cartesianPoint2d()); + + if (!h2.shouldBeClipped()) + expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h2.cartesianPoint2d()); + + if (h2.shouldBeClipped() ^ h3.shouldBeClipped()) + expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h2, h3).cartesianPoint2d()); + + if (!h3.shouldBeClipped()) + expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h3.cartesianPoint2d()); + + if (h3.shouldBeClipped() ^ h4.shouldBeClipped()) + expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h3, h4).cartesianPoint2d()); + + if (!h4.shouldBeClipped()) + expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h4.cartesianPoint2d()); + + if (h4.shouldBeClipped() ^ h1.shouldBeClipped()) + expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h4, h1).cartesianPoint2d()); + + return FloatRect(FloatPoint(xmin, ymin), FloatSize(xmax - xmin, ymax - ymin)); +} + +FloatQuad CCMathUtil::mapQuad(const WebTransformationMatrix& transform, const FloatQuad& q, bool& clipped) +{ + if (transform.isIdentityOrTranslation()) { + FloatQuad mappedQuad(q); + mappedQuad.move(static_cast<float>(transform.m41()), static_cast<float>(transform.m42())); + clipped = false; + return mappedQuad; + } + + HomogeneousCoordinate h1 = mapHomogeneousPoint(transform, q.p1()); + HomogeneousCoordinate h2 = mapHomogeneousPoint(transform, q.p2()); + HomogeneousCoordinate h3 = mapHomogeneousPoint(transform, q.p3()); + HomogeneousCoordinate h4 = mapHomogeneousPoint(transform, q.p4()); + + clipped = h1.shouldBeClipped() || h2.shouldBeClipped() || h3.shouldBeClipped() || h4.shouldBeClipped(); + + // Result will be invalid if clipped == true. But, compute it anyway just in case, to emulate existing behavior. + return FloatQuad(h1.cartesianPoint2d(), h2.cartesianPoint2d(), h3.cartesianPoint2d(), h4.cartesianPoint2d()); +} + +FloatPoint CCMathUtil::mapPoint(const WebTransformationMatrix& transform, const FloatPoint& p, bool& clipped) +{ + HomogeneousCoordinate h = mapHomogeneousPoint(transform, p); + + if (h.w > 0) { + clipped = false; + return h.cartesianPoint2d(); + } + + // The cartesian coordinates will be invalid after dividing by w. + clipped = true; + + // Avoid dividing by w if w == 0. + if (!h.w) + return FloatPoint(); + + // This return value will be invalid because clipped == true, but (1) users of this + // code should be ignoring the return value when clipped == true anyway, and (2) this + // behavior is more consistent with existing behavior of WebKit transforms if the user + // really does not ignore the return value. + return h.cartesianPoint2d(); +} + +FloatPoint3D CCMathUtil::mapPoint(const WebTransformationMatrix& transform, const FloatPoint3D& p, bool& clipped) +{ + HomogeneousCoordinate h = mapHomogeneousPoint(transform, p); + + if (h.w > 0) { + clipped = false; + return h.cartesianPoint3d(); + } + + // The cartesian coordinates will be invalid after dividing by w. + clipped = true; + + // Avoid dividing by w if w == 0. + if (!h.w) + return FloatPoint3D(); + + // This return value will be invalid because clipped == true, but (1) users of this + // code should be ignoring the return value when clipped == true anyway, and (2) this + // behavior is more consistent with existing behavior of WebKit transforms if the user + // really does not ignore the return value. + return h.cartesianPoint3d(); +} + +FloatQuad CCMathUtil::projectQuad(const WebTransformationMatrix& transform, const FloatQuad& q, bool& clipped) +{ + FloatQuad projectedQuad; + bool clippedPoint; + projectedQuad.setP1(projectPoint(transform, q.p1(), clippedPoint)); + clipped = clippedPoint; + projectedQuad.setP2(projectPoint(transform, q.p2(), clippedPoint)); + clipped |= clippedPoint; + projectedQuad.setP3(projectPoint(transform, q.p3(), clippedPoint)); + clipped |= clippedPoint; + projectedQuad.setP4(projectPoint(transform, q.p4(), clippedPoint)); + clipped |= clippedPoint; + + return projectedQuad; +} + +FloatPoint CCMathUtil::projectPoint(const WebTransformationMatrix& transform, const FloatPoint& p, bool& clipped) +{ + HomogeneousCoordinate h = projectHomogeneousPoint(transform, p); + + if (h.w > 0) { + // The cartesian coordinates will be valid in this case. + clipped = false; + return h.cartesianPoint2d(); + } + + // The cartesian coordinates will be invalid after dividing by w. + clipped = true; + + // Avoid dividing by w if w == 0. + if (!h.w) + return FloatPoint(); + + // This return value will be invalid because clipped == true, but (1) users of this + // code should be ignoring the return value when clipped == true anyway, and (2) this + // behavior is more consistent with existing behavior of WebKit transforms if the user + // really does not ignore the return value. + return h.cartesianPoint2d(); +} + +void CCMathUtil::flattenTransformTo2d(WebTransformationMatrix& transform) +{ + // Set both the 3rd row and 3rd column to (0, 0, 1, 0). + // + // One useful interpretation of doing this operation: + // - For x and y values, the new transform behaves effectively like an orthographic + // projection was added to the matrix sequence. + // - For z values, the new transform overrides any effect that the transform had on + // z, and instead it preserves the z value for any points that are transformed. + // - Because of linearity of transforms, this flattened transform also preserves the + // effect that any subsequent (post-multiplied) transforms would have on z values. + // + transform.setM13(0); + transform.setM23(0); + transform.setM31(0); + transform.setM32(0); + transform.setM33(1); + transform.setM34(0); + transform.setM43(0); +} + +float CCMathUtil::smallestAngleBetweenVectors(const FloatSize& v1, const FloatSize& v2) +{ + float dotProduct = (v1.width() * v2.width() + v1.height() * v2.height()) / (v1.diagonalLength() * v2.diagonalLength()); + // Clamp to compensate for rounding errors. + dotProduct = std::max(-1.f, std::min(1.f, dotProduct)); + return rad2deg(acosf(dotProduct)); +} + +FloatSize CCMathUtil::projectVector(const FloatSize& source, const FloatSize& destination) +{ + float sourceDotDestination = source.width() * destination.width() + source.height() * destination.height(); + float projectedLength = sourceDotDestination / destination.diagonalLengthSquared(); + return FloatSize(projectedLength * destination.width(), projectedLength * destination.height()); +} + +} // namespace WebCore diff --git a/cc/CCMathUtil.h b/cc/CCMathUtil.h new file mode 100644 index 0000000..834acf5 --- /dev/null +++ b/cc/CCMathUtil.h @@ -0,0 +1,109 @@ +// Copyright 2012 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. + +#ifndef CCMathUtil_h +#define CCMathUtil_h + +#include "FloatPoint.h" +#include "FloatPoint3D.h" + +namespace WebKit { +class WebTransformationMatrix; +} + +namespace WebCore { + +class IntRect; +class FloatRect; +class FloatQuad; + +struct HomogeneousCoordinate { + HomogeneousCoordinate(double newX, double newY, double newZ, double newW) + : x(newX) + , y(newY) + , z(newZ) + , w(newW) + { + } + + bool shouldBeClipped() const + { + return w <= 0; + } + + FloatPoint cartesianPoint2d() const + { + if (w == 1) + return FloatPoint(x, y); + + // For now, because this code is used privately only by CCMathUtil, it should never be called when w == 0, and we do not yet need to handle that case. + ASSERT(w); + double invW = 1.0 / w; + return FloatPoint(x * invW, y * invW); + } + + FloatPoint3D cartesianPoint3d() const + { + if (w == 1) + return FloatPoint3D(x, y, z); + + // For now, because this code is used privately only by CCMathUtil, it should never be called when w == 0, and we do not yet need to handle that case. + ASSERT(w); + double invW = 1.0 / w; + return FloatPoint3D(x * invW, y * invW, z * invW); + } + + double x; + double y; + double z; + double w; +}; + +// This class contains math helper functionality that does not belong in WebCore. +// It is possible that this functionality should be migrated to WebCore eventually. +class CCMathUtil { +public: + + // Background: WebTransformationMatrix code in WebCore does not do the right thing in + // mapRect / mapQuad / projectQuad when there is a perspective projection that causes + // one of the transformed vertices to go to w < 0. In those cases, it is necessary to + // perform clipping in homogeneous coordinates, after applying the transform, before + // dividing-by-w to convert to cartesian coordinates. + // + // These functions return the axis-aligned rect that encloses the correctly clipped, + // transformed polygon. + static IntRect mapClippedRect(const WebKit::WebTransformationMatrix&, const IntRect&); + static FloatRect mapClippedRect(const WebKit::WebTransformationMatrix&, const FloatRect&); + static FloatRect projectClippedRect(const WebKit::WebTransformationMatrix&, const FloatRect&); + + // Returns an array of vertices that represent the clipped polygon. After returning, indexes from + // 0 to numVerticesInClippedQuad are valid in the clippedQuad array. Note that + // numVerticesInClippedQuad may be zero, which means the entire quad was clipped, and + // none of the vertices in the array are valid. + static void mapClippedQuad(const WebKit::WebTransformationMatrix&, const FloatQuad& srcQuad, FloatPoint clippedQuad[8], int& numVerticesInClippedQuad); + + static FloatRect computeEnclosingRectOfVertices(FloatPoint vertices[], int numVertices); + static FloatRect computeEnclosingClippedRect(const HomogeneousCoordinate& h1, const HomogeneousCoordinate& h2, const HomogeneousCoordinate& h3, const HomogeneousCoordinate& h4); + + // NOTE: These functions do not do correct clipping against w = 0 plane, but they + // correctly detect the clipped condition via the boolean clipped. + static FloatQuad mapQuad(const WebKit::WebTransformationMatrix&, const FloatQuad&, bool& clipped); + static FloatPoint mapPoint(const WebKit::WebTransformationMatrix&, const FloatPoint&, bool& clipped); + static FloatPoint3D mapPoint(const WebKit::WebTransformationMatrix&, const FloatPoint3D&, bool& clipped); + static FloatQuad projectQuad(const WebKit::WebTransformationMatrix&, const FloatQuad&, bool& clipped); + static FloatPoint projectPoint(const WebKit::WebTransformationMatrix&, const FloatPoint&, bool& clipped); + + static void flattenTransformTo2d(WebKit::WebTransformationMatrix&); + + // Returns the smallest angle between the given two vectors in degrees. Neither vector is + // assumed to be normalized. + static float smallestAngleBetweenVectors(const FloatSize&, const FloatSize&); + + // Projects the |source| vector onto |destination|. Neither vector is assumed to be normalized. + static FloatSize projectVector(const FloatSize& source, const FloatSize& destination); +}; + +} // namespace WebCore + +#endif // #define CCMathUtil_h diff --git a/cc/CCMathUtilTest.cpp b/cc/CCMathUtilTest.cpp new file mode 100644 index 0000000..e56fa3c --- /dev/null +++ b/cc/CCMathUtilTest.cpp @@ -0,0 +1,182 @@ +// Copyright 2012 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 "CCMathUtil.h" + +#include "CCLayerTreeTestCommon.h" +#include "FloatRect.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/WebTransformationMatrix.h> + +using namespace WebCore; +using WebKit::WebTransformationMatrix; + +namespace { + +TEST(CCMathUtilTest, verifyBackfaceVisibilityBasicCases) +{ + WebTransformationMatrix transform; + + transform.makeIdentity(); + EXPECT_FALSE(transform.isBackFaceVisible()); + + transform.makeIdentity(); + transform.rotate3d(0, 80, 0); + EXPECT_FALSE(transform.isBackFaceVisible()); + + transform.makeIdentity(); + transform.rotate3d(0, 100, 0); + EXPECT_TRUE(transform.isBackFaceVisible()); + + // Edge case, 90 degree rotation should return false. + transform.makeIdentity(); + transform.rotate3d(0, 90, 0); + EXPECT_FALSE(transform.isBackFaceVisible()); +} + +TEST(CCMathUtilTest, verifyBackfaceVisibilityForPerspective) +{ + WebTransformationMatrix layerSpaceToProjectionPlane; + + // This tests if isBackFaceVisible works properly under perspective transforms. + // Specifically, layers that may have their back face visible in orthographic + // projection, may not actually have back face visible under perspective projection. + + // Case 1: Layer is rotated by slightly more than 90 degrees, at the center of the + // prespective projection. In this case, the layer's back-side is visible to + // the camera. + layerSpaceToProjectionPlane.makeIdentity(); + layerSpaceToProjectionPlane.applyPerspective(1); + layerSpaceToProjectionPlane.translate3d(0, 0, 0); + layerSpaceToProjectionPlane.rotate3d(0, 100, 0); + EXPECT_TRUE(layerSpaceToProjectionPlane.isBackFaceVisible()); + + // Case 2: Layer is rotated by slightly more than 90 degrees, but shifted off to the + // side of the camera. Because of the wide field-of-view, the layer's front + // side is still visible. + // + // |<-- front side of layer is visible to perspective camera + // \ | / + // \ | / + // \| / + // | / + // |\ /<-- camera field of view + // | \ / + // back side of layer -->| \ / + // \./ <-- camera origin + // + layerSpaceToProjectionPlane.makeIdentity(); + layerSpaceToProjectionPlane.applyPerspective(1); + layerSpaceToProjectionPlane.translate3d(-10, 0, 0); + layerSpaceToProjectionPlane.rotate3d(0, 100, 0); + EXPECT_FALSE(layerSpaceToProjectionPlane.isBackFaceVisible()); + + // Case 3: Additionally rotating the layer by 180 degrees should of course show the + // opposite result of case 2. + layerSpaceToProjectionPlane.rotate3d(0, 180, 0); + EXPECT_TRUE(layerSpaceToProjectionPlane.isBackFaceVisible()); +} + +TEST(CCMathUtilTest, verifyProjectionOfPerpendicularPlane) +{ + // In this case, the m33() element of the transform becomes zero, which could cause a + // divide-by-zero when projecting points/quads. + + WebTransformationMatrix transform; + transform.makeIdentity(); + transform.setM33(0); + + FloatRect rect = FloatRect(0, 0, 1, 1); + FloatRect projectedRect = CCMathUtil::projectClippedRect(transform, rect); + + EXPECT_EQ(0, projectedRect.x()); + EXPECT_EQ(0, projectedRect.y()); + EXPECT_TRUE(projectedRect.isEmpty()); +} + +TEST(CCMathUtilTest, verifyEnclosingClippedRectUsesCorrectInitialBounds) +{ + HomogeneousCoordinate h1(-100, -100, 0, 1); + HomogeneousCoordinate h2(-10, -10, 0, 1); + HomogeneousCoordinate h3(10, 10, 0, -1); + HomogeneousCoordinate h4(100, 100, 0, -1); + + // The bounds of the enclosing clipped rect should be -100 to -10 for both x and y. + // However, if there is a bug where the initial xmin/xmax/ymin/ymax are initialized to + // numeric_limits<float>::min() (which is zero, not -flt_max) then the enclosing + // clipped rect will be computed incorrectly. + FloatRect result = CCMathUtil::computeEnclosingClippedRect(h1, h2, h3, h4); + + EXPECT_FLOAT_RECT_EQ(FloatRect(FloatPoint(-100, -100), FloatSize(90, 90)), result); +} + +TEST(CCMathUtilTest, verifyEnclosingRectOfVerticesUsesCorrectInitialBounds) +{ + FloatPoint vertices[3]; + int numVertices = 3; + + vertices[0] = FloatPoint(-10, -100); + vertices[1] = FloatPoint(-100, -10); + vertices[2] = FloatPoint(-30, -30); + + // The bounds of the enclosing rect should be -100 to -10 for both x and y. However, + // if there is a bug where the initial xmin/xmax/ymin/ymax are initialized to + // numeric_limits<float>::min() (which is zero, not -flt_max) then the enclosing + // clipped rect will be computed incorrectly. + FloatRect result = CCMathUtil::computeEnclosingRectOfVertices(vertices, numVertices); + + EXPECT_FLOAT_RECT_EQ(FloatRect(FloatPoint(-100, -100), FloatSize(90, 90)), result); +} + +TEST(CCMathUtilTest, smallestAngleBetweenVectors) +{ + FloatSize x(1, 0); + FloatSize y(0, 1); + FloatSize testVector(0.5, 0.5); + + // Orthogonal vectors are at an angle of 90 degress. + EXPECT_EQ(90, CCMathUtil::smallestAngleBetweenVectors(x, y)); + + // A vector makes a zero angle with itself. + EXPECT_EQ(0, CCMathUtil::smallestAngleBetweenVectors(x, x)); + EXPECT_EQ(0, CCMathUtil::smallestAngleBetweenVectors(y, y)); + EXPECT_EQ(0, CCMathUtil::smallestAngleBetweenVectors(testVector, testVector)); + + // Parallel but reversed vectors are at 180 degrees. + EXPECT_FLOAT_EQ(180, CCMathUtil::smallestAngleBetweenVectors(x, -x)); + EXPECT_FLOAT_EQ(180, CCMathUtil::smallestAngleBetweenVectors(y, -y)); + EXPECT_FLOAT_EQ(180, CCMathUtil::smallestAngleBetweenVectors(testVector, -testVector)); + + // The test vector is at a known angle. + EXPECT_FLOAT_EQ(45, floor(CCMathUtil::smallestAngleBetweenVectors(testVector, x))); + EXPECT_FLOAT_EQ(45, floor(CCMathUtil::smallestAngleBetweenVectors(testVector, y))); +} + +TEST(CCMathUtilTest, vectorProjection) +{ + FloatSize x(1, 0); + FloatSize y(0, 1); + FloatSize testVector(0.3f, 0.7f); + + // Orthogonal vectors project to a zero vector. + EXPECT_EQ(FloatSize(0, 0), CCMathUtil::projectVector(x, y)); + EXPECT_EQ(FloatSize(0, 0), CCMathUtil::projectVector(y, x)); + + // Projecting a vector onto the orthonormal basis gives the corresponding component of the + // vector. + EXPECT_EQ(FloatSize(testVector.width(), 0), CCMathUtil::projectVector(testVector, x)); + EXPECT_EQ(FloatSize(0, testVector.height()), CCMathUtil::projectVector(testVector, y)); + + // Finally check than an arbitrary vector projected to another one gives a vector parallel to + // the second vector. + FloatSize targetVector(0.5, 0.2f); + FloatSize projectedVector = CCMathUtil::projectVector(testVector, targetVector); + EXPECT_EQ(projectedVector.width() / targetVector.width(), + projectedVector.height() / targetVector.height()); +} + +} // namespace diff --git a/cc/CCOcclusionTracker.cpp b/cc/CCOcclusionTracker.cpp new file mode 100644 index 0000000..2bd1181 --- /dev/null +++ b/cc/CCOcclusionTracker.cpp @@ -0,0 +1,482 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCOcclusionTracker.h" + +#include "CCLayerImpl.h" +#include "CCMathUtil.h" +#include "CCOverdrawMetrics.h" +#include "LayerChromium.h" +#include <algorithm> + +using namespace std; +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +template<typename LayerType, typename RenderSurfaceType> +CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::CCOcclusionTrackerBase(IntRect rootTargetRect, bool recordMetricsForFrame) + : m_rootTargetRect(rootTargetRect) + , m_overdrawMetrics(CCOverdrawMetrics::create(recordMetricsForFrame)) + , m_occludingScreenSpaceRects(0) +{ +} + +template<typename LayerType, typename RenderSurfaceType> +void CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::enterLayer(const CCLayerIteratorPosition<LayerType>& layerIterator) +{ + LayerType* renderTarget = layerIterator.targetRenderSurfaceLayer; + + if (layerIterator.representsItself) + enterRenderTarget(renderTarget); + else if (layerIterator.representsTargetRenderSurface) + finishedRenderTarget(renderTarget); +} + +template<typename LayerType, typename RenderSurfaceType> +void CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::leaveLayer(const CCLayerIteratorPosition<LayerType>& layerIterator) +{ + LayerType* renderTarget = layerIterator.targetRenderSurfaceLayer; + + if (layerIterator.representsItself) + markOccludedBehindLayer(layerIterator.currentLayer); + else if (layerIterator.representsContributingRenderSurface) + leaveToRenderTarget(renderTarget); +} + +template<typename LayerType, typename RenderSurfaceType> +void CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::enterRenderTarget(const LayerType* newTarget) +{ + if (!m_stack.isEmpty() && m_stack.last().target == newTarget) + return; + + const LayerType* oldTarget = m_stack.isEmpty() ? 0 : m_stack.last().target; + const RenderSurfaceType* oldAncestorThatMovesPixels = !oldTarget ? 0 : oldTarget->renderSurface()->nearestAncestorThatMovesPixels(); + const RenderSurfaceType* newAncestorThatMovesPixels = newTarget->renderSurface()->nearestAncestorThatMovesPixels(); + + m_stack.append(StackObject(newTarget)); + + // We copy the screen occlusion into the new RenderSurface subtree, but we never copy in the + // target occlusion, since we are looking at a new RenderSurface target. + + // If we are entering a subtree that is going to move pixels around, then the occlusion we've computed + // so far won't apply to the pixels we're drawing here in the same way. We discard the occlusion thus + // far to be safe, and ensure we don't cull any pixels that are moved such that they become visible. + bool enteringSubtreeThatMovesPixels = newAncestorThatMovesPixels && newAncestorThatMovesPixels != oldAncestorThatMovesPixels; + + bool copyScreenOcclusionForward = m_stack.size() > 1 && !enteringSubtreeThatMovesPixels; + if (copyScreenOcclusionForward) { + int lastIndex = m_stack.size() - 1; + m_stack[lastIndex].occlusionInScreen = m_stack[lastIndex - 1].occlusionInScreen; + } +} + +static inline bool layerOpacityKnown(const LayerChromium* layer) { return !layer->drawOpacityIsAnimating(); } +static inline bool layerOpacityKnown(const CCLayerImpl*) { return true; } +static inline bool layerTransformsToTargetKnown(const LayerChromium* layer) { return !layer->drawTransformIsAnimating(); } +static inline bool layerTransformsToTargetKnown(const CCLayerImpl*) { return true; } +static inline bool layerTransformsToScreenKnown(const LayerChromium* layer) { return !layer->screenSpaceTransformIsAnimating(); } +static inline bool layerTransformsToScreenKnown(const CCLayerImpl*) { return true; } + +static inline bool surfaceOpacityKnown(const RenderSurfaceChromium* surface) { return !surface->drawOpacityIsAnimating(); } +static inline bool surfaceOpacityKnown(const CCRenderSurface*) { return true; } +static inline bool surfaceTransformsToTargetKnown(const RenderSurfaceChromium* surface) { return !surface->targetSurfaceTransformsAreAnimating(); } +static inline bool surfaceTransformsToTargetKnown(const CCRenderSurface*) { return true; } +static inline bool surfaceTransformsToScreenKnown(const RenderSurfaceChromium* surface) { return !surface->screenSpaceTransformsAreAnimating(); } +static inline bool surfaceTransformsToScreenKnown(const CCRenderSurface*) { return true; } + +static inline bool layerIsInUnsorted3dRenderingContext(const LayerChromium* layer) { return layer->parent() && layer->parent()->preserves3D(); } +static inline bool layerIsInUnsorted3dRenderingContext(const CCLayerImpl*) { return false; } + +template<typename LayerType, typename RenderSurfaceType> +void CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::finishedRenderTarget(const LayerType* finishedTarget) +{ + // Make sure we know about the target surface. + enterRenderTarget(finishedTarget); + + RenderSurfaceType* surface = finishedTarget->renderSurface(); + + // If the occlusion within the surface can not be applied to things outside of the surface's subtree, then clear the occlusion here so it won't be used. + if (finishedTarget->maskLayer() || !surfaceOpacityKnown(surface) || surface->drawOpacity() < 1 || finishedTarget->filters().hasFilterThatAffectsOpacity()) { + m_stack.last().occlusionInScreen = Region(); + m_stack.last().occlusionInTarget = Region(); + } else { + if (!surfaceTransformsToTargetKnown(surface)) + m_stack.last().occlusionInTarget = Region(); + if (!surfaceTransformsToScreenKnown(surface)) + m_stack.last().occlusionInScreen = Region(); + } +} + +template<typename RenderSurfaceType> +static inline Region transformSurfaceOpaqueRegion(const RenderSurfaceType* surface, const Region& region, const WebTransformationMatrix& transform) +{ + // Verify that rects within the |surface| will remain rects in its target surface after applying |transform|. If this is true, then + // apply |transform| to each rect within |region| in order to transform the entire Region. + + bool clipped; + FloatQuad transformedBoundsQuad = CCMathUtil::mapQuad(transform, FloatQuad(region.bounds()), clipped); + // FIXME: Find a rect interior to each transformed quad. + if (clipped || !transformedBoundsQuad.isRectilinear()) + return Region(); + + Region transformedRegion; + + Vector<IntRect> rects = region.rects(); + for (size_t i = 0; i < rects.size(); ++i) { + // We've already checked for clipping in the mapQuad call above, these calls should not clip anything further. + IntRect transformedRect = enclosedIntRect(CCMathUtil::mapClippedRect(transform, FloatRect(rects[i]))); + if (!surface->clipRect().isEmpty()) + transformedRect.intersect(surface->clipRect()); + transformedRegion.unite(transformedRect); + } + return transformedRegion; +} + +static inline void reduceOcclusion(const IntRect& affectedArea, const IntRect& expandedPixel, Region& occlusion) +{ + if (affectedArea.isEmpty()) + return; + + Region affectedOcclusion = intersect(occlusion, affectedArea); + Vector<IntRect> affectedOcclusionRects = affectedOcclusion.rects(); + + occlusion.subtract(affectedArea); + for (size_t j = 0; j < affectedOcclusionRects.size(); ++j) { + IntRect& occlusionRect = affectedOcclusionRects[j]; + + // Shrink the rect by expanding the non-opaque pixels outside the rect. + + // The expandedPixel is the IntRect for a single pixel after being + // expanded by filters on the layer. The original pixel would be + // IntRect(0, 0, 1, 1), and the expanded pixel is the rect, relative + // to this original rect, that the original pixel can influence after + // being filtered. + // To convert the expandedPixel IntRect back to filter outsets: + // x = -leftOutset + // width = leftOutset + rightOutset + // maxX = x + width = -leftOutset + leftOutset + rightOutset = rightOutset + + // The leftOutset of the filters moves pixels on the right side of + // the occlusionRect into it, shrinking its right edge. + int shrinkLeft = occlusionRect.x() == affectedArea.x() ? 0 : expandedPixel.maxX(); + int shrinkTop = occlusionRect.y() == affectedArea.y() ? 0 : expandedPixel.maxY(); + int shrinkRight = occlusionRect.maxX() == affectedArea.maxX() ? 0 : -expandedPixel.x(); + int shrinkBottom = occlusionRect.maxY() == affectedArea.maxY() ? 0 : -expandedPixel.y(); + + occlusionRect.move(shrinkLeft, shrinkTop); + occlusionRect.contract(shrinkLeft + shrinkRight, shrinkTop + shrinkBottom); + + occlusion.unite(occlusionRect); + } +} + +template<typename LayerType> +static void reduceOcclusionBelowSurface(LayerType* contributingLayer, const IntRect& surfaceRect, const WebTransformationMatrix& surfaceTransform, LayerType* renderTarget, Region& occlusionInTarget, Region& occlusionInScreen) +{ + if (surfaceRect.isEmpty()) + return; + + IntRect boundsInTarget = enclosingIntRect(CCMathUtil::mapClippedRect(surfaceTransform, FloatRect(surfaceRect))); + if (!contributingLayer->renderSurface()->clipRect().isEmpty()) + boundsInTarget.intersect(contributingLayer->renderSurface()->clipRect()); + + int outsetTop, outsetRight, outsetBottom, outsetLeft; + contributingLayer->backgroundFilters().getOutsets(outsetTop, outsetRight, outsetBottom, outsetLeft); + + // The filter can move pixels from outside of the clip, so allow affectedArea to expand outside the clip. + boundsInTarget.move(-outsetLeft, -outsetTop); + boundsInTarget.expand(outsetLeft + outsetRight, outsetTop + outsetBottom); + + IntRect boundsInScreen = enclosingIntRect(CCMathUtil::mapClippedRect(renderTarget->renderSurface()->screenSpaceTransform(), FloatRect(boundsInTarget))); + + IntRect filterOutsetsInTarget(-outsetLeft, -outsetTop, outsetLeft + outsetRight, outsetTop + outsetBottom); + IntRect filterOutsetsInScreen = enclosingIntRect(CCMathUtil::mapClippedRect(renderTarget->renderSurface()->screenSpaceTransform(), FloatRect(filterOutsetsInTarget))); + + reduceOcclusion(boundsInTarget, filterOutsetsInTarget, occlusionInTarget); + reduceOcclusion(boundsInScreen, filterOutsetsInScreen, occlusionInScreen); +} + +template<typename LayerType, typename RenderSurfaceType> +void CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::leaveToRenderTarget(const LayerType* newTarget) +{ + int lastIndex = m_stack.size() - 1; + bool surfaceWillBeAtTopAfterPop = m_stack.size() > 1 && m_stack[lastIndex - 1].target == newTarget; + + // We merge the screen occlusion from the current RenderSurface subtree out to its parent target RenderSurface. + // The target occlusion can be merged out as well but needs to be transformed to the new target. + + const LayerType* oldTarget = m_stack[lastIndex].target; + const RenderSurfaceType* oldSurface = oldTarget->renderSurface(); + Region oldTargetOcclusionInNewTarget = transformSurfaceOpaqueRegion<RenderSurfaceType>(oldSurface, m_stack[lastIndex].occlusionInTarget, oldSurface->drawTransform()); + if (oldTarget->hasReplica() && !oldTarget->replicaHasMask()) + oldTargetOcclusionInNewTarget.unite(transformSurfaceOpaqueRegion<RenderSurfaceType>(oldSurface, m_stack[lastIndex].occlusionInTarget, oldSurface->replicaDrawTransform())); + + IntRect unoccludedSurfaceRect; + IntRect unoccludedReplicaRect; + if (oldTarget->backgroundFilters().hasFilterThatMovesPixels()) { + unoccludedSurfaceRect = unoccludedContributingSurfaceContentRect(oldTarget, false, oldSurface->contentRect()); + if (oldTarget->hasReplica()) + unoccludedReplicaRect = unoccludedContributingSurfaceContentRect(oldTarget, true, oldSurface->contentRect()); + } + + if (surfaceWillBeAtTopAfterPop) { + // Merge the top of the stack down. + m_stack[lastIndex - 1].occlusionInScreen.unite(m_stack[lastIndex].occlusionInScreen); + m_stack[lastIndex - 1].occlusionInTarget.unite(oldTargetOcclusionInNewTarget); + m_stack.removeLast(); + } else { + // Replace the top of the stack with the new pushed surface. Copy the occluded screen region to the top. + m_stack.last().target = newTarget; + m_stack.last().occlusionInTarget = oldTargetOcclusionInNewTarget; + } + + if (oldTarget->backgroundFilters().hasFilterThatMovesPixels()) { + reduceOcclusionBelowSurface(oldTarget, unoccludedSurfaceRect, oldSurface->drawTransform(), newTarget, m_stack.last().occlusionInTarget, m_stack.last().occlusionInScreen); + if (oldTarget->hasReplica()) + reduceOcclusionBelowSurface(oldTarget, unoccludedReplicaRect, oldSurface->replicaDrawTransform(), newTarget, m_stack.last().occlusionInTarget, m_stack.last().occlusionInScreen); + } +} + +// FIXME: Remove usePaintTracking when paint tracking is on for paint culling. +template<typename LayerType> +static inline void addOcclusionBehindLayer(Region& region, const LayerType* layer, const WebTransformationMatrix& transform, const Region& opaqueContents, const IntRect& clipRectInTarget, const IntSize& minimumTrackingSize, Vector<IntRect>* occludingScreenSpaceRects) +{ + ASSERT(layer->visibleContentRect().contains(opaqueContents.bounds())); + + bool clipped; + FloatQuad visibleTransformedQuad = CCMathUtil::mapQuad(transform, FloatQuad(layer->visibleContentRect()), clipped); + // FIXME: Find a rect interior to each transformed quad. + if (clipped || !visibleTransformedQuad.isRectilinear()) + return; + + Vector<IntRect> contentRects = opaqueContents.rects(); + for (size_t i = 0; i < contentRects.size(); ++i) { + // We've already checked for clipping in the mapQuad call above, these calls should not clip anything further. + IntRect transformedRect = enclosedIntRect(CCMathUtil::mapClippedRect(transform, FloatRect(contentRects[i]))); + transformedRect.intersect(clipRectInTarget); + if (transformedRect.width() >= minimumTrackingSize.width() || transformedRect.height() >= minimumTrackingSize.height()) { + if (occludingScreenSpaceRects) + occludingScreenSpaceRects->append(transformedRect); + region.unite(transformedRect); + } + } +} + +template<typename LayerType, typename RenderSurfaceType> +void CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::markOccludedBehindLayer(const LayerType* layer) +{ + ASSERT(!m_stack.isEmpty()); + ASSERT(layer->renderTarget() == m_stack.last().target); + if (m_stack.isEmpty()) + return; + + if (!layerOpacityKnown(layer) || layer->drawOpacity() < 1) + return; + + if (layerIsInUnsorted3dRenderingContext(layer)) + return; + + Region opaqueContents = layer->visibleContentOpaqueRegion(); + if (opaqueContents.isEmpty()) + return; + + IntRect clipRectInTarget = layerClipRectInTarget(layer); + if (layerTransformsToTargetKnown(layer)) + addOcclusionBehindLayer<LayerType>(m_stack.last().occlusionInTarget, layer, layer->drawTransform(), opaqueContents, clipRectInTarget, m_minimumTrackingSize, 0); + + // We must clip the occlusion within the layer's clipRectInTarget within screen space as well. If the clip rect can't be moved to screen space and + // remain rectilinear, then we don't add any occlusion in screen space. + + if (layerTransformsToScreenKnown(layer)) { + WebTransformationMatrix targetToScreenTransform = m_stack.last().target->renderSurface()->screenSpaceTransform(); + bool clipped; + FloatQuad clipQuadInScreen = CCMathUtil::mapQuad(targetToScreenTransform, FloatQuad(FloatRect(clipRectInTarget)), clipped); + // FIXME: Find a rect interior to the transformed clip quad. + if (clipped || !clipQuadInScreen.isRectilinear()) + return; + IntRect clipRectInScreen = intersection(m_rootTargetRect, enclosedIntRect(clipQuadInScreen.boundingBox())); + addOcclusionBehindLayer<LayerType>(m_stack.last().occlusionInScreen, layer, layer->screenSpaceTransform(), opaqueContents, clipRectInScreen, m_minimumTrackingSize, m_occludingScreenSpaceRects); + } +} + +static inline bool testContentRectOccluded(const IntRect& contentRect, const WebTransformationMatrix& contentSpaceTransform, const IntRect& clipRectInTarget, const Region& occlusion) +{ + FloatRect transformedRect = CCMathUtil::mapClippedRect(contentSpaceTransform, FloatRect(contentRect)); + // Take the enclosingIntRect, as we want to include partial pixels in the test. + IntRect targetRect = intersection(enclosingIntRect(transformedRect), clipRectInTarget); + return targetRect.isEmpty() || occlusion.contains(targetRect); +} + +template<typename LayerType, typename RenderSurfaceType> +bool CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::occluded(const LayerType* layer, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface) const +{ + if (hasOcclusionFromOutsideTargetSurface) + *hasOcclusionFromOutsideTargetSurface = false; + + ASSERT(!m_stack.isEmpty()); + if (m_stack.isEmpty()) + return false; + if (contentRect.isEmpty()) + return true; + + ASSERT(layer->renderTarget() == m_stack.last().target); + + if (layerTransformsToTargetKnown(layer) && testContentRectOccluded(contentRect, layer->drawTransform(), layerClipRectInTarget(layer), m_stack.last().occlusionInTarget)) + return true; + + if (layerTransformsToScreenKnown(layer) && testContentRectOccluded(contentRect, layer->screenSpaceTransform(), m_rootTargetRect, m_stack.last().occlusionInScreen)) { + if (hasOcclusionFromOutsideTargetSurface) + *hasOcclusionFromOutsideTargetSurface = true; + return true; + } + + return false; +} + +// Determines what portion of rect, if any, is unoccluded (not occluded by region). If +// the resulting unoccluded region is not rectangular, we return a rect containing it. +static inline IntRect rectSubtractRegion(const IntRect& rect, const Region& region) +{ + Region rectRegion(rect); + rectRegion.subtract(region); + return rectRegion.bounds(); +} + +static inline IntRect computeUnoccludedContentRect(const IntRect& contentRect, const WebTransformationMatrix& contentSpaceTransform, const IntRect& clipRectInTarget, const Region& occlusion) +{ + if (!contentSpaceTransform.isInvertible()) + return contentRect; + + // Take the enclosingIntRect at each step, as we want to contain any unoccluded partial pixels in the resulting IntRect. + FloatRect transformedRect = CCMathUtil::mapClippedRect(contentSpaceTransform, FloatRect(contentRect)); + IntRect shrunkRect = rectSubtractRegion(intersection(enclosingIntRect(transformedRect), clipRectInTarget), occlusion); + IntRect unoccludedRect = enclosingIntRect(CCMathUtil::projectClippedRect(contentSpaceTransform.inverse(), FloatRect(shrunkRect))); + // The rect back in content space is a bounding box and may extend outside of the original contentRect, so clamp it to the contentRectBounds. + return intersection(unoccludedRect, contentRect); +} + +template<typename LayerType, typename RenderSurfaceType> +IntRect CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::unoccludedContentRect(const LayerType* layer, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface) const +{ + ASSERT(!m_stack.isEmpty()); + if (m_stack.isEmpty()) + return contentRect; + if (contentRect.isEmpty()) + return contentRect; + + ASSERT(layer->renderTarget() == m_stack.last().target); + + // We want to return a rect that contains all the visible parts of |contentRect| in both screen space and in the target surface. + // So we find the visible parts of |contentRect| in each space, and take the intersection. + + IntRect unoccludedInScreen = contentRect; + if (layerTransformsToScreenKnown(layer)) + unoccludedInScreen = computeUnoccludedContentRect(contentRect, layer->screenSpaceTransform(), m_rootTargetRect, m_stack.last().occlusionInScreen); + + IntRect unoccludedInTarget = contentRect; + if (layerTransformsToTargetKnown(layer)) + unoccludedInTarget = computeUnoccludedContentRect(contentRect, layer->drawTransform(), layerClipRectInTarget(layer), m_stack.last().occlusionInTarget); + + if (hasOcclusionFromOutsideTargetSurface) + *hasOcclusionFromOutsideTargetSurface = (intersection(unoccludedInScreen, unoccludedInTarget) != unoccludedInTarget); + + return intersection(unoccludedInScreen, unoccludedInTarget); +} + +template<typename LayerType, typename RenderSurfaceType> +IntRect CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::unoccludedContributingSurfaceContentRect(const LayerType* layer, bool forReplica, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface) const +{ + ASSERT(!m_stack.isEmpty()); + // The layer is a contributing renderTarget so it should have a surface. + ASSERT(layer->renderSurface()); + // The layer is a contributing renderTarget so its target should be itself. + ASSERT(layer->renderTarget() == layer); + // The layer should not be the root, else what is is contributing to? + ASSERT(layer->parent()); + // This should be called while the layer is still considered the current target in the occlusion tracker. + ASSERT(layer == m_stack.last().target); + + if (contentRect.isEmpty()) + return contentRect; + + RenderSurfaceType* surface = layer->renderSurface(); + + IntRect surfaceClipRect = surface->clipRect(); + if (surfaceClipRect.isEmpty()) { + LayerType* contributingSurfaceRenderTarget = layer->parent()->renderTarget(); + surfaceClipRect = intersection(contributingSurfaceRenderTarget->renderSurface()->contentRect(), enclosingIntRect(surface->drawableContentRect())); + } + + // A contributing surface doesn't get occluded by things inside its own surface, so only things outside the surface can occlude it. That occlusion is + // found just below the top of the stack (if it exists). + bool hasOcclusion = m_stack.size() > 1; + + const WebTransformationMatrix& transformToScreen = forReplica ? surface->replicaScreenSpaceTransform() : surface->screenSpaceTransform(); + const WebTransformationMatrix& transformToTarget = forReplica ? surface->replicaDrawTransform() : surface->drawTransform(); + + IntRect unoccludedInScreen = contentRect; + if (surfaceTransformsToScreenKnown(surface)) { + if (hasOcclusion) { + const StackObject& secondLast = m_stack[m_stack.size() - 2]; + unoccludedInScreen = computeUnoccludedContentRect(contentRect, transformToScreen, m_rootTargetRect, secondLast.occlusionInScreen); + } else + unoccludedInScreen = computeUnoccludedContentRect(contentRect, transformToScreen, m_rootTargetRect, Region()); + } + + IntRect unoccludedInTarget = contentRect; + if (surfaceTransformsToTargetKnown(surface)) { + if (hasOcclusion) { + const StackObject& secondLast = m_stack[m_stack.size() - 2]; + unoccludedInTarget = computeUnoccludedContentRect(contentRect, transformToTarget, surfaceClipRect, secondLast.occlusionInTarget); + } else + unoccludedInTarget = computeUnoccludedContentRect(contentRect, transformToTarget, surfaceClipRect, Region()); + } + + if (hasOcclusionFromOutsideTargetSurface) + *hasOcclusionFromOutsideTargetSurface = (intersection(unoccludedInScreen, unoccludedInTarget) != unoccludedInTarget); + + return intersection(unoccludedInScreen, unoccludedInTarget); +} + +template<typename LayerType, typename RenderSurfaceType> +IntRect CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::layerClipRectInTarget(const LayerType* layer) const +{ + // FIXME: we could remove this helper function, but unit tests currently override this + // function, and they need to be verified/adjusted before this can be removed. + return layer->drawableContentRect(); +} + +// Declare the possible functions here for the linker. +template CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::CCOcclusionTrackerBase(IntRect rootTargetRect, bool recordMetricsForFrame); +template void CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::enterLayer(const CCLayerIteratorPosition<LayerChromium>&); +template void CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::leaveLayer(const CCLayerIteratorPosition<LayerChromium>&); +template void CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::enterRenderTarget(const LayerChromium* newTarget); +template void CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::finishedRenderTarget(const LayerChromium* finishedTarget); +template void CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::leaveToRenderTarget(const LayerChromium* newTarget); +template void CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::markOccludedBehindLayer(const LayerChromium*); +template bool CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::occluded(const LayerChromium*, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface) const; +template IntRect CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::unoccludedContentRect(const LayerChromium*, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface) const; +template IntRect CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::unoccludedContributingSurfaceContentRect(const LayerChromium*, bool forReplica, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface) const; +template IntRect CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::layerClipRectInTarget(const LayerChromium*) const; + +template CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::CCOcclusionTrackerBase(IntRect rootTargetRect, bool recordMetricsForFrame); +template void CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::enterLayer(const CCLayerIteratorPosition<CCLayerImpl>&); +template void CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::leaveLayer(const CCLayerIteratorPosition<CCLayerImpl>&); +template void CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::enterRenderTarget(const CCLayerImpl* newTarget); +template void CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::finishedRenderTarget(const CCLayerImpl* finishedTarget); +template void CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::leaveToRenderTarget(const CCLayerImpl* newTarget); +template void CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::markOccludedBehindLayer(const CCLayerImpl*); +template bool CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::occluded(const CCLayerImpl*, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface) const; +template IntRect CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::unoccludedContentRect(const CCLayerImpl*, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface) const; +template IntRect CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::unoccludedContributingSurfaceContentRect(const CCLayerImpl*, bool forReplica, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface) const; +template IntRect CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::layerClipRectInTarget(const CCLayerImpl*) const; + + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCOcclusionTracker.h b/cc/CCOcclusionTracker.h new file mode 100644 index 0000000..c923056 --- /dev/null +++ b/cc/CCOcclusionTracker.h @@ -0,0 +1,103 @@ +// Copyright 2012 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. + +#ifndef CCOcclusionTracker_h +#define CCOcclusionTracker_h + +#include "CCLayerIterator.h" +#include "FloatQuad.h" +#include "Region.h" + +namespace WebCore { +class CCOverdrawMetrics; +class CCLayerImpl; +class CCRenderSurface; +class LayerChromium; +class RenderSurfaceChromium; + +// This class is used to track occlusion of layers while traversing them in a front-to-back order. As each layer is visited, one of the +// methods in this class is called to notify it about the current target surface. +// Then, occlusion in the content space of the current layer may be queried, via methods such as occluded() and unoccludedContentRect(). +// If the current layer owns a RenderSurface, then occlusion on that RenderSurface may also be queried via surfaceOccluded() and surfaceUnoccludedContentRect(). +// Finally, once finished with the layer, occlusion behind the layer should be marked by calling markOccludedBehindLayer(). +template<typename LayerType, typename RenderSurfaceType> +class CCOcclusionTrackerBase { + WTF_MAKE_NONCOPYABLE(CCOcclusionTrackerBase); +public: + CCOcclusionTrackerBase(IntRect rootTargetRect, bool recordMetricsForFrame); + + // Called at the beginning of each step in the CCLayerIterator's front-to-back traversal. + void enterLayer(const CCLayerIteratorPosition<LayerType>&); + // Called at the end of each step in the CCLayerIterator's front-to-back traversal. + void leaveLayer(const CCLayerIteratorPosition<LayerType>&); + + // Returns true if the given rect in content space for the layer is fully occluded in either screen space or the layer's target surface. + bool occluded(const LayerType*, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface = 0) const; + // Gives an unoccluded sub-rect of |contentRect| in the content space of the layer. Used when considering occlusion for a layer that paints/draws something. + IntRect unoccludedContentRect(const LayerType*, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface = 0) const; + + // Gives an unoccluded sub-rect of |contentRect| in the content space of the renderTarget owned by the layer. + // Used when considering occlusion for a contributing surface that is rendering into another target. + IntRect unoccludedContributingSurfaceContentRect(const LayerType*, bool forReplica, const IntRect& contentRect, bool* hasOcclusionFromOutsideTargetSurface = 0) const; + + // Report operations for recording overdraw metrics. + CCOverdrawMetrics& overdrawMetrics() const { return *m_overdrawMetrics.get(); } + + // Gives the region of the screen that is not occluded by something opaque. + Region computeVisibleRegionInScreen() const { return subtract(Region(m_rootTargetRect), m_stack.last().occlusionInScreen); } + + void setMinimumTrackingSize(const IntSize& size) { m_minimumTrackingSize = size; } + + // The following is used for visualization purposes. + void setOccludingScreenSpaceRectsContainer(Vector<IntRect>* rects) { m_occludingScreenSpaceRects = rects; } + +protected: + struct StackObject { + StackObject() : target(0) { } + StackObject(const LayerType* target) : target(target) { } + const LayerType* target; + Region occlusionInScreen; + Region occlusionInTarget; + }; + + // The stack holds occluded regions for subtrees in the RenderSurface-Layer tree, so that when we leave a subtree we may + // apply a mask to it, but not to the parts outside the subtree. + // - The first time we see a new subtree under a target, we add that target to the top of the stack. This can happen as a layer representing itself, or as a target surface. + // - When we visit a target surface, we apply its mask to its subtree, which is at the top of the stack. + // - When we visit a layer representing itself, we add its occlusion to the current subtree, which is at the top of the stack. + // - When we visit a layer representing a contributing surface, the current target will never be the top of the stack since we just came from the contributing surface. + // We merge the occlusion at the top of the stack with the new current subtree. This new target is pushed onto the stack if not already there. + Vector<StackObject, 1> m_stack; + + // Allow tests to override this. + virtual IntRect layerClipRectInTarget(const LayerType*) const; + +private: + // Called when visiting a layer representing itself. If the target was not already current, then this indicates we have entered a new surface subtree. + void enterRenderTarget(const LayerType* newTarget); + + // Called when visiting a layer representing a target surface. This indicates we have visited all the layers within the surface, and we may + // perform any surface-wide operations. + void finishedRenderTarget(const LayerType* finishedTarget); + + // Called when visiting a layer representing a contributing surface. This indicates that we are leaving our current surface, and + // entering the new one. We then perform any operations required for merging results from the child subtree into its parent. + void leaveToRenderTarget(const LayerType* newTarget); + + // Add the layer's occlusion to the tracked state. + void markOccludedBehindLayer(const LayerType*); + + IntRect m_rootTargetRect; + OwnPtr<CCOverdrawMetrics> m_overdrawMetrics; + IntSize m_minimumTrackingSize; + + // This is used for visualizing the occlusion tracking process. + Vector<IntRect>* m_occludingScreenSpaceRects; +}; + +typedef CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium> CCOcclusionTracker; +typedef CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface> CCOcclusionTrackerImpl; + +} +#endif // CCOcclusionTracker_h diff --git a/cc/CCOcclusionTrackerTest.cpp b/cc/CCOcclusionTrackerTest.cpp new file mode 100644 index 0000000..cb0eefe6 --- /dev/null +++ b/cc/CCOcclusionTrackerTest.cpp @@ -0,0 +1,3005 @@ +// Copyright 2012 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 "CCOcclusionTracker.h" + +#include "CCAnimationTestCommon.h" +#include "CCLayerAnimationController.h" +#include "CCLayerImpl.h" +#include "CCLayerTreeHostCommon.h" +#include "CCLayerTreeTestCommon.h" +#include "CCMathUtil.h" +#include "CCOcclusionTrackerTestCommon.h" +#include "CCOverdrawMetrics.h" +#include "CCSingleThreadProxy.h" +#include "LayerChromium.h" +#include "Region.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/WebFilterOperation.h> +#include <public/WebFilterOperations.h> +#include <public/WebTransformationMatrix.h> + +using namespace WebCore; +using namespace WebKit; +using namespace WebKitTests; + +namespace { + +class TestContentLayerChromium : public LayerChromium { +public: + TestContentLayerChromium() + : LayerChromium() + , m_overrideOpaqueContentsRect(false) + { + } + + virtual bool drawsContent() const OVERRIDE { return true; } + virtual Region visibleContentOpaqueRegion() const OVERRIDE + { + if (m_overrideOpaqueContentsRect) + return intersection(m_opaqueContentsRect, visibleContentRect()); + return LayerChromium::visibleContentOpaqueRegion(); + } + void setOpaqueContentsRect(const IntRect& opaqueContentsRect) + { + m_overrideOpaqueContentsRect = true; + m_opaqueContentsRect = opaqueContentsRect; + } + +private: + bool m_overrideOpaqueContentsRect; + IntRect m_opaqueContentsRect; +}; + +class TestContentLayerImpl : public CCLayerImpl { +public: + TestContentLayerImpl(int id) + : CCLayerImpl(id) + , m_overrideOpaqueContentsRect(false) + { + setDrawsContent(true); + } + + virtual Region visibleContentOpaqueRegion() const OVERRIDE + { + if (m_overrideOpaqueContentsRect) + return intersection(m_opaqueContentsRect, visibleContentRect()); + return CCLayerImpl::visibleContentOpaqueRegion(); + } + void setOpaqueContentsRect(const IntRect& opaqueContentsRect) + { + m_overrideOpaqueContentsRect = true; + m_opaqueContentsRect = opaqueContentsRect; + } + +private: + bool m_overrideOpaqueContentsRect; + IntRect m_opaqueContentsRect; +}; + +template<typename LayerType, typename RenderSurfaceType> +class TestCCOcclusionTrackerWithClip : public TestCCOcclusionTrackerBase<LayerType, RenderSurfaceType> { +public: + TestCCOcclusionTrackerWithClip(IntRect viewportRect, bool recordMetricsForFrame = false) + : TestCCOcclusionTrackerBase<LayerType, RenderSurfaceType>(viewportRect, recordMetricsForFrame) + , m_overrideLayerClipRect(false) + { + } + + void setLayerClipRect(const IntRect& rect) { m_overrideLayerClipRect = true; m_layerClipRect = rect;} + void useDefaultLayerClipRect() { m_overrideLayerClipRect = false; } + +protected: + virtual IntRect layerClipRectInTarget(const LayerType* layer) const { return m_overrideLayerClipRect ? m_layerClipRect : CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::layerClipRectInTarget(layer); } + +private: + bool m_overrideLayerClipRect; + IntRect m_layerClipRect; +}; + +struct CCOcclusionTrackerTestMainThreadTypes { + typedef LayerChromium LayerType; + typedef RenderSurfaceChromium RenderSurfaceType; + typedef TestContentLayerChromium ContentLayerType; + typedef RefPtr<LayerChromium> LayerPtrType; + typedef PassRefPtr<LayerChromium> PassLayerPtrType; + typedef RefPtr<ContentLayerType> ContentLayerPtrType; + typedef PassRefPtr<ContentLayerType> PassContentLayerPtrType; + typedef CCLayerIterator<LayerChromium, Vector<RefPtr<LayerChromium> >, RenderSurfaceChromium, CCLayerIteratorActions::FrontToBack> LayerIterator; + typedef CCOcclusionTracker OcclusionTrackerType; + + static PassLayerPtrType createLayer() + { + return LayerChromium::create(); + } + static PassContentLayerPtrType createContentLayer() { return adoptRef(new ContentLayerType()); } +}; + +struct CCOcclusionTrackerTestImplThreadTypes { + typedef CCLayerImpl LayerType; + typedef CCRenderSurface RenderSurfaceType; + typedef TestContentLayerImpl ContentLayerType; + typedef OwnPtr<CCLayerImpl> LayerPtrType; + typedef PassOwnPtr<CCLayerImpl> PassLayerPtrType; + typedef OwnPtr<ContentLayerType> ContentLayerPtrType; + typedef PassOwnPtr<ContentLayerType> PassContentLayerPtrType; + typedef CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, CCLayerIteratorActions::FrontToBack> LayerIterator; + typedef CCOcclusionTrackerImpl OcclusionTrackerType; + + static PassLayerPtrType createLayer() { return CCLayerImpl::create(nextCCLayerImplId++); } + static PassContentLayerPtrType createContentLayer() { return adoptPtr(new ContentLayerType(nextCCLayerImplId++)); } + static int nextCCLayerImplId; +}; + +int CCOcclusionTrackerTestImplThreadTypes::nextCCLayerImplId = 1; + +template<typename Types, bool opaqueLayers> +class CCOcclusionTrackerTest : public testing::Test { +protected: + CCOcclusionTrackerTest() + : testing::Test() + { } + + virtual void runMyTest() = 0; + + virtual void TearDown() + { + m_root.clear(); + m_renderSurfaceLayerListChromium.clear(); + m_renderSurfaceLayerListImpl.clear(); + m_replicaLayers.clear(); + m_maskLayers.clear(); + CCLayerTreeHost::setNeedsFilterContext(false); + } + + typename Types::ContentLayerType* createRoot(const WebTransformationMatrix& transform, const FloatPoint& position, const IntSize& bounds) + { + typename Types::ContentLayerPtrType layer(Types::createContentLayer()); + typename Types::ContentLayerType* layerPtr = layer.get(); + setProperties(layerPtr, transform, position, bounds); + + ASSERT(!m_root); + m_root = layer.release(); + return layerPtr; + } + + typename Types::LayerType* createLayer(typename Types::LayerType* parent, const WebTransformationMatrix& transform, const FloatPoint& position, const IntSize& bounds) + { + typename Types::LayerPtrType layer(Types::createLayer()); + typename Types::LayerType* layerPtr = layer.get(); + setProperties(layerPtr, transform, position, bounds); + parent->addChild(layer.release()); + return layerPtr; + } + + typename Types::LayerType* createSurface(typename Types::LayerType* parent, const WebTransformationMatrix& transform, const FloatPoint& position, const IntSize& bounds) + { + typename Types::LayerType* layer = createLayer(parent, transform, position, bounds); + WebFilterOperations filters; + filters.append(WebFilterOperation::createGrayscaleFilter(0.5)); + layer->setFilters(filters); + return layer; + } + + typename Types::ContentLayerType* createDrawingLayer(typename Types::LayerType* parent, const WebTransformationMatrix& transform, const FloatPoint& position, const IntSize& bounds, bool opaque) + { + typename Types::ContentLayerPtrType layer(Types::createContentLayer()); + typename Types::ContentLayerType* layerPtr = layer.get(); + setProperties(layerPtr, transform, position, bounds); + + if (opaqueLayers) + layerPtr->setOpaque(opaque); + else { + layerPtr->setOpaque(false); + if (opaque) + layerPtr->setOpaqueContentsRect(IntRect(IntPoint(), bounds)); + else + layerPtr->setOpaqueContentsRect(IntRect()); + } + + parent->addChild(layer.release()); + return layerPtr; + } + + typename Types::LayerType* createReplicaLayer(typename Types::LayerType* owningLayer, const WebTransformationMatrix& transform, const FloatPoint& position, const IntSize& bounds) + { + typename Types::ContentLayerPtrType layer(Types::createContentLayer()); + typename Types::ContentLayerType* layerPtr = layer.get(); + setProperties(layerPtr, transform, position, bounds); + setReplica(owningLayer, layer.release()); + return layerPtr; + } + + typename Types::LayerType* createMaskLayer(typename Types::LayerType* owningLayer, const IntSize& bounds) + { + typename Types::ContentLayerPtrType layer(Types::createContentLayer()); + typename Types::ContentLayerType* layerPtr = layer.get(); + setProperties(layerPtr, identityMatrix, FloatPoint(), bounds); + setMask(owningLayer, layer.release()); + return layerPtr; + } + + typename Types::ContentLayerType* createDrawingSurface(typename Types::LayerType* parent, const WebTransformationMatrix& transform, const FloatPoint& position, const IntSize& bounds, bool opaque) + { + typename Types::ContentLayerType* layer = createDrawingLayer(parent, transform, position, bounds, opaque); + WebFilterOperations filters; + filters.append(WebFilterOperation::createGrayscaleFilter(0.5)); + layer->setFilters(filters); + return layer; + } + + void calcDrawEtc(TestContentLayerImpl* root) + { + ASSERT(root == m_root.get()); + int dummyMaxTextureSize = 512; + CCLayerSorter layerSorter; + + ASSERT(!root->renderSurface()); + + CCLayerTreeHostCommon::calculateDrawTransforms(root, root->bounds(), 1, &layerSorter, dummyMaxTextureSize, m_renderSurfaceLayerListImpl); + CCLayerTreeHostCommon::calculateVisibleRects(m_renderSurfaceLayerListImpl); + + m_layerIterator = m_layerIteratorBegin = Types::LayerIterator::begin(&m_renderSurfaceLayerListImpl); + } + + void calcDrawEtc(TestContentLayerChromium* root) + { + ASSERT(root == m_root.get()); + int dummyMaxTextureSize = 512; + + ASSERT(!root->renderSurface()); + + CCLayerTreeHostCommon::calculateDrawTransforms(root, root->bounds(), 1, dummyMaxTextureSize, m_renderSurfaceLayerListChromium); + CCLayerTreeHostCommon::calculateVisibleRects(m_renderSurfaceLayerListChromium); + + m_layerIterator = m_layerIteratorBegin = Types::LayerIterator::begin(&m_renderSurfaceLayerListChromium); + } + + void enterLayer(typename Types::LayerType* layer, typename Types::OcclusionTrackerType& occlusion) + { + ASSERT_EQ(layer, *m_layerIterator); + ASSERT_TRUE(m_layerIterator.representsItself()); + occlusion.enterLayer(m_layerIterator); + } + + void leaveLayer(typename Types::LayerType* layer, typename Types::OcclusionTrackerType& occlusion) + { + ASSERT_EQ(layer, *m_layerIterator); + ASSERT_TRUE(m_layerIterator.representsItself()); + occlusion.leaveLayer(m_layerIterator); + ++m_layerIterator; + } + + void visitLayer(typename Types::LayerType* layer, typename Types::OcclusionTrackerType& occlusion) + { + enterLayer(layer, occlusion); + leaveLayer(layer, occlusion); + } + + void enterContributingSurface(typename Types::LayerType* layer, typename Types::OcclusionTrackerType& occlusion) + { + ASSERT_EQ(layer, *m_layerIterator); + ASSERT_TRUE(m_layerIterator.representsTargetRenderSurface()); + occlusion.enterLayer(m_layerIterator); + occlusion.leaveLayer(m_layerIterator); + ++m_layerIterator; + ASSERT_TRUE(m_layerIterator.representsContributingRenderSurface()); + occlusion.enterLayer(m_layerIterator); + } + + void leaveContributingSurface(typename Types::LayerType* layer, typename Types::OcclusionTrackerType& occlusion) + { + ASSERT_EQ(layer, *m_layerIterator); + ASSERT_TRUE(m_layerIterator.representsContributingRenderSurface()); + occlusion.leaveLayer(m_layerIterator); + ++m_layerIterator; + } + + void visitContributingSurface(typename Types::LayerType* layer, typename Types::OcclusionTrackerType& occlusion) + { + enterContributingSurface(layer, occlusion); + leaveContributingSurface(layer, occlusion); + } + + void resetLayerIterator() + { + m_layerIterator = m_layerIteratorBegin; + } + + const WebTransformationMatrix identityMatrix; + +private: + void setBaseProperties(typename Types::LayerType* layer, const WebTransformationMatrix& transform, const FloatPoint& position, const IntSize& bounds) + { + layer->setTransform(transform); + layer->setSublayerTransform(WebTransformationMatrix()); + layer->setAnchorPoint(FloatPoint(0, 0)); + layer->setPosition(position); + layer->setBounds(bounds); + } + + void setProperties(LayerChromium* layer, const WebTransformationMatrix& transform, const FloatPoint& position, const IntSize& bounds) + { + setBaseProperties(layer, transform, position, bounds); + } + + void setProperties(CCLayerImpl* layer, const WebTransformationMatrix& transform, const FloatPoint& position, const IntSize& bounds) + { + setBaseProperties(layer, transform, position, bounds); + + layer->setContentBounds(layer->bounds()); + } + + void setReplica(LayerChromium* owningLayer, PassRefPtr<LayerChromium> layer) + { + owningLayer->setReplicaLayer(layer.get()); + m_replicaLayers.append(layer); + } + + void setReplica(CCLayerImpl* owningLayer, PassOwnPtr<CCLayerImpl> layer) + { + owningLayer->setReplicaLayer(layer); + } + + void setMask(LayerChromium* owningLayer, PassRefPtr<LayerChromium> layer) + { + owningLayer->setMaskLayer(layer.get()); + m_maskLayers.append(layer); + } + + void setMask(CCLayerImpl* owningLayer, PassOwnPtr<CCLayerImpl> layer) + { + owningLayer->setMaskLayer(layer); + } + + // These hold ownership of the layers for the duration of the test. + typename Types::LayerPtrType m_root; + Vector<RefPtr<LayerChromium> > m_renderSurfaceLayerListChromium; + Vector<CCLayerImpl*> m_renderSurfaceLayerListImpl; + typename Types::LayerIterator m_layerIteratorBegin; + typename Types::LayerIterator m_layerIterator; + typename Types::LayerType* m_lastLayerVisited; + Vector<RefPtr<LayerChromium> > m_replicaLayers; + Vector<RefPtr<LayerChromium> > m_maskLayers; +}; + +#define RUN_TEST_MAIN_THREAD_OPAQUE_LAYERS(ClassName) \ + class ClassName##MainThreadOpaqueLayers : public ClassName<CCOcclusionTrackerTestMainThreadTypes, true> { \ + public: \ + ClassName##MainThreadOpaqueLayers() : ClassName<CCOcclusionTrackerTestMainThreadTypes, true>() { } \ + }; \ + TEST_F(ClassName##MainThreadOpaqueLayers, runTest) { runMyTest(); } +#define RUN_TEST_MAIN_THREAD_OPAQUE_PAINTS(ClassName) \ + class ClassName##MainThreadOpaquePaints : public ClassName<CCOcclusionTrackerTestMainThreadTypes, false> { \ + public: \ + ClassName##MainThreadOpaquePaints() : ClassName<CCOcclusionTrackerTestMainThreadTypes, false>() { } \ + }; \ + TEST_F(ClassName##MainThreadOpaquePaints, runTest) { runMyTest(); } + +#define RUN_TEST_IMPL_THREAD_OPAQUE_LAYERS(ClassName) \ + class ClassName##ImplThreadOpaqueLayers : public ClassName<CCOcclusionTrackerTestImplThreadTypes, true> { \ + DebugScopedSetImplThread impl; \ + public: \ + ClassName##ImplThreadOpaqueLayers() : ClassName<CCOcclusionTrackerTestImplThreadTypes, true>() { } \ + }; \ + TEST_F(ClassName##ImplThreadOpaqueLayers, runTest) { runMyTest(); } +#define RUN_TEST_IMPL_THREAD_OPAQUE_PAINTS(ClassName) \ + class ClassName##ImplThreadOpaquePaints : public ClassName<CCOcclusionTrackerTestImplThreadTypes, false> { \ + DebugScopedSetImplThread impl; \ + public: \ + ClassName##ImplThreadOpaquePaints() : ClassName<CCOcclusionTrackerTestImplThreadTypes, false>() { } \ + }; \ + TEST_F(ClassName##ImplThreadOpaquePaints, runTest) { runMyTest(); } + +#define ALL_CCOCCLUSIONTRACKER_TEST(ClassName) \ + RUN_TEST_MAIN_THREAD_OPAQUE_LAYERS(ClassName) \ + RUN_TEST_MAIN_THREAD_OPAQUE_PAINTS(ClassName) \ + RUN_TEST_IMPL_THREAD_OPAQUE_LAYERS(ClassName) \ + RUN_TEST_IMPL_THREAD_OPAQUE_PAINTS(ClassName) + +#define MAIN_THREAD_TEST(ClassName) \ + RUN_TEST_MAIN_THREAD_OPAQUE_LAYERS(ClassName) + +#define IMPL_THREAD_TEST(ClassName) \ + RUN_TEST_IMPL_THREAD_OPAQUE_LAYERS(ClassName) + +#define MAIN_AND_IMPL_THREAD_TEST(ClassName) \ + RUN_TEST_MAIN_THREAD_OPAQUE_LAYERS(ClassName) \ + RUN_TEST_IMPL_THREAD_OPAQUE_LAYERS(ClassName) + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestIdentityTransforms : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(30, 30), IntSize(500, 500), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 30, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(29, 30, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 29, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(31, 30, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 31, 70, 70))); + + occlusion.useDefaultLayerClipRect(); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 30, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(29, 30, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 29, 70, 70))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(31, 30, 70, 70))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 31, 70, 70))); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(30, 30, 70, 70)).isEmpty()); + EXPECT_INT_RECT_EQ(IntRect(29, 30, 1, 70), occlusion.unoccludedContentRect(parent, IntRect(29, 30, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(29, 29, 70, 70), occlusion.unoccludedContentRect(parent, IntRect(29, 29, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(30, 29, 70, 1), occlusion.unoccludedContentRect(parent, IntRect(30, 29, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(31, 29, 70, 70), occlusion.unoccludedContentRect(parent, IntRect(31, 29, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(100, 30, 1, 70), occlusion.unoccludedContentRect(parent, IntRect(31, 30, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(31, 31, 70, 70), occlusion.unoccludedContentRect(parent, IntRect(31, 31, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(30, 100, 70, 1), occlusion.unoccludedContentRect(parent, IntRect(30, 31, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(29, 31, 70, 70), occlusion.unoccludedContentRect(parent, IntRect(29, 31, 70, 70))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestIdentityTransforms); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestRotatedChild : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix layerTransform; + layerTransform.translate(250, 250); + layerTransform.rotate(90); + layerTransform.translate(-250, -250); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(parent, layerTransform, FloatPoint(30, 30), IntSize(500, 500), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 30, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(29, 30, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 29, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(31, 30, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 31, 70, 70))); + + occlusion.useDefaultLayerClipRect(); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 30, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(29, 30, 70, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 29, 70, 70))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(31, 30, 70, 70))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 31, 70, 70))); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(30, 30, 70, 70)).isEmpty()); + EXPECT_INT_RECT_EQ(IntRect(29, 30, 1, 70), occlusion.unoccludedContentRect(parent, IntRect(29, 30, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(29, 29, 70, 70), occlusion.unoccludedContentRect(parent, IntRect(29, 29, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(30, 29, 70, 1), occlusion.unoccludedContentRect(parent, IntRect(30, 29, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(31, 29, 70, 70), occlusion.unoccludedContentRect(parent, IntRect(31, 29, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(100, 30, 1, 70), occlusion.unoccludedContentRect(parent, IntRect(31, 30, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(31, 31, 70, 70), occlusion.unoccludedContentRect(parent, IntRect(31, 31, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(30, 100, 70, 1), occlusion.unoccludedContentRect(parent, IntRect(30, 31, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(29, 31, 70, 70), occlusion.unoccludedContentRect(parent, IntRect(29, 31, 70, 70))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestRotatedChild); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestTranslatedChild : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix layerTransform; + layerTransform.translate(20, 20); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(parent, layerTransform, FloatPoint(30, 30), IntSize(500, 500), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(50, 50, 50, 50), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(50, 50, 50, 50), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(50, 50, 50, 50))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(49, 50, 50, 50))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(50, 49, 50, 50))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(51, 50, 50, 50))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(50, 51, 50, 50))); + + occlusion.useDefaultLayerClipRect(); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(50, 50, 50, 50))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(49, 50, 50, 50))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(50, 49, 50, 50))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(51, 50, 50, 50))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(50, 51, 50, 50))); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(50, 50, 50, 50)).isEmpty()); + EXPECT_INT_RECT_EQ(IntRect(49, 50, 1, 50), occlusion.unoccludedContentRect(parent, IntRect(49, 50, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(49, 49, 50, 50), occlusion.unoccludedContentRect(parent, IntRect(49, 49, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(50, 49, 50, 1), occlusion.unoccludedContentRect(parent, IntRect(50, 49, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(51, 49, 50, 50), occlusion.unoccludedContentRect(parent, IntRect(51, 49, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(100, 50, 1, 50), occlusion.unoccludedContentRect(parent, IntRect(51, 50, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(51, 51, 50, 50), occlusion.unoccludedContentRect(parent, IntRect(51, 51, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(50, 100, 50, 1), occlusion.unoccludedContentRect(parent, IntRect(50, 51, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(49, 51, 50, 50), occlusion.unoccludedContentRect(parent, IntRect(49, 51, 50, 50))); + + occlusion.useDefaultLayerClipRect(); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(50, 50, 50, 50)).isEmpty()); + EXPECT_INT_RECT_EQ(IntRect(49, 50, 1, 50), occlusion.unoccludedContentRect(parent, IntRect(49, 50, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(49, 49, 50, 50), occlusion.unoccludedContentRect(parent, IntRect(49, 49, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(50, 49, 50, 1), occlusion.unoccludedContentRect(parent, IntRect(50, 49, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(51, 49, 49, 1), occlusion.unoccludedContentRect(parent, IntRect(51, 49, 50, 50))); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(51, 50, 50, 50)).isEmpty()); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(51, 51, 50, 50)).isEmpty()); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(50, 51, 50, 50)).isEmpty()); + EXPECT_INT_RECT_EQ(IntRect(49, 51, 1, 49), occlusion.unoccludedContentRect(parent, IntRect(49, 51, 50, 50))); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestTranslatedChild); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestChildInRotatedChild : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix childTransform; + childTransform.translate(250, 250); + childTransform.rotate(90); + childTransform.translate(-250, -250); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::LayerType* child = this->createLayer(parent, childTransform, FloatPoint(30, 30), IntSize(500, 500)); + child->setMasksToBounds(true); + typename Types::ContentLayerType* layer = this->createDrawingLayer(child, this->identityMatrix, FloatPoint(10, 10), IntSize(500, 500), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(layer, occlusion); + this->enterContributingSurface(child, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 40, 70, 60), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(10, 430, 60, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->leaveContributingSurface(child, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 40, 70, 60), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(30, 40, 70, 60), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 40, 70, 60))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(29, 40, 70, 60))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 39, 70, 60))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(31, 40, 70, 60))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 41, 70, 60))); + + occlusion.useDefaultLayerClipRect(); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 40, 70, 60))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(29, 40, 70, 60))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 39, 70, 60))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(31, 40, 70, 60))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 41, 70, 60))); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + + /* Justification for the above occlusion from |layer|: + 100 + +---------------------+ +---------------------+ + | | | |30 Visible region of |layer|: ///// + | 30 | rotate(90) | | + | 30 + ---------------------------------+ | +---------------------------------+ + 100 | | 10 | | ==> | | |10 | + | |10+---------------------------------+ | +---------------------------------+ | + | | | | | | | | |///////////////| 420 | | + | | | | | | | | |///////////////|60 | | + | | | | | | | | |///////////////| | | + +----|--|-------------+ | | +--|--|---------------+ | | + | | | | 20|10| 70 | | + | | | | | | | | + | | | |500 | | | | + | | | | | | | | + | | | | | | | | + | | | | | | | | + | | | | | | |10| + +--|-------------------------------+ | | +------------------------------|--+ + | | | 490 | + +---------------------------------+ +---------------------------------+ + 500 500 + */ + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestChildInRotatedChild); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestVisitTargetTwoTimes : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix childTransform; + childTransform.translate(250, 250); + childTransform.rotate(90); + childTransform.translate(-250, -250); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::LayerType* child = this->createLayer(parent, childTransform, FloatPoint(30, 30), IntSize(500, 500)); + child->setMasksToBounds(true); + typename Types::ContentLayerType* layer = this->createDrawingLayer(child, this->identityMatrix, FloatPoint(10, 10), IntSize(500, 500), true); + // |child2| makes |parent|'s surface get considered by CCOcclusionTracker first, instead of |child|'s. This exercises different code in + // leaveToTargetRenderSurface, as the target surface has already been seen. + typename Types::ContentLayerType* child2 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(30, 30), IntSize(60, 20), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(-10, -10, 1000, 1000)); + + this->visitLayer(child2, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 30, 60, 20), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(30, 30, 60, 20), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->visitLayer(layer, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(10, 430, 60, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->enterContributingSurface(child, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(10, 430, 60, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + // Occlusion in |child2| should get merged with the |child| surface we are leaving now. + this->leaveContributingSurface(child, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 30, 70, 70))); + EXPECT_INT_RECT_EQ(IntRect(90, 30, 10, 10), occlusion.unoccludedContentRect(parent, IntRect(30, 30, 70, 70))); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 30, 60, 10))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(29, 30, 60, 10))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 29, 60, 10))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(31, 30, 60, 10))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 31, 60, 10))); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 40, 70, 60))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(29, 40, 70, 60))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 39, 70, 60))); + + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(30, 30, 60, 10)).isEmpty()); + EXPECT_INT_RECT_EQ(IntRect(29, 30, 1, 10), occlusion.unoccludedContentRect(parent, IntRect(29, 30, 60, 10))); + EXPECT_INT_RECT_EQ(IntRect(30, 29, 60, 1), occlusion.unoccludedContentRect(parent, IntRect(30, 29, 60, 10))); + EXPECT_INT_RECT_EQ(IntRect(90, 30, 1, 10), occlusion.unoccludedContentRect(parent, IntRect(31, 30, 60, 10))); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(30, 31, 60, 10)).isEmpty()); + + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(30, 40, 70, 60)).isEmpty()); + EXPECT_INT_RECT_EQ(IntRect(29, 40, 1, 60), occlusion.unoccludedContentRect(parent, IntRect(29, 40, 70, 60))); + // This rect is mostly occluded by |child2|. + EXPECT_INT_RECT_EQ(IntRect(90, 39, 10, 1), occlusion.unoccludedContentRect(parent, IntRect(30, 39, 70, 60))); + // This rect extends past top/right ends of |child2|. + EXPECT_INT_RECT_EQ(IntRect(30, 29, 70, 11), occlusion.unoccludedContentRect(parent, IntRect(30, 29, 70, 70))); + // This rect extends past left/right ends of |child2|. + EXPECT_INT_RECT_EQ(IntRect(20, 39, 80, 60), occlusion.unoccludedContentRect(parent, IntRect(20, 39, 80, 60))); + EXPECT_INT_RECT_EQ(IntRect(100, 40, 1, 60), occlusion.unoccludedContentRect(parent, IntRect(31, 40, 70, 60))); + EXPECT_INT_RECT_EQ(IntRect(30, 100, 70, 1), occlusion.unoccludedContentRect(parent, IntRect(30, 41, 70, 60))); + + /* Justification for the above occlusion from |layer|: + 100 + +---------------------+ +---------------------+ + | | | |30 Visible region of |layer|: ///// + | 30 | rotate(90) | 30 60 | |child2|: \\\\\ + | 30 + ------------+--------------------+ | 30 +------------+--------------------+ + 100 | | 10 | | | ==> | |\\\\\\\\\\\\| |10 | + | |10+----------|----------------------+ | +--|\\\\\\\\\\\\|-----------------+ | + | + ------------+ | | | | | +------------+//| 420 | | + | | | | | | | | |///////////////|60 | | + | | | | | | | | |///////////////| | | + +----|--|-------------+ | | +--|--|---------------+ | | + | | | | 20|10| 70 | | + | | | | | | | | + | | | |500 | | | | + | | | | | | | | + | | | | | | | | + | | | | | | | | + | | | | | | |10| + +--|-------------------------------+ | | +------------------------------|--+ + | | | 490 | + +---------------------------------+ +---------------------------------+ + 500 500 + */ + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestVisitTargetTwoTimes); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestSurfaceRotatedOffAxis : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix childTransform; + childTransform.translate(250, 250); + childTransform.rotate(95); + childTransform.translate(-250, -250); + + WebTransformationMatrix layerTransform; + layerTransform.translate(10, 10); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::LayerType* child = this->createLayer(parent, childTransform, FloatPoint(30, 30), IntSize(500, 500)); + child->setMasksToBounds(true); + typename Types::ContentLayerType* layer = this->createDrawingLayer(child, layerTransform, FloatPoint(0, 0), IntSize(500, 500), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + IntRect clippedLayerInChild = CCMathUtil::mapClippedRect(layerTransform, layer->visibleContentRect()); + + this->visitLayer(layer, occlusion); + this->enterContributingSurface(child, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(0u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(clippedLayerInChild, occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(child, clippedLayerInChild)); + EXPECT_TRUE(occlusion.unoccludedContentRect(child, clippedLayerInChild).isEmpty()); + clippedLayerInChild.move(-1, 0); + EXPECT_FALSE(occlusion.occluded(child, clippedLayerInChild)); + EXPECT_FALSE(occlusion.unoccludedContentRect(child, clippedLayerInChild).isEmpty()); + clippedLayerInChild.move(1, 0); + clippedLayerInChild.move(1, 0); + EXPECT_FALSE(occlusion.occluded(child, clippedLayerInChild)); + EXPECT_FALSE(occlusion.unoccludedContentRect(child, clippedLayerInChild).isEmpty()); + clippedLayerInChild.move(-1, 0); + clippedLayerInChild.move(0, -1); + EXPECT_FALSE(occlusion.occluded(child, clippedLayerInChild)); + EXPECT_FALSE(occlusion.unoccludedContentRect(child, clippedLayerInChild).isEmpty()); + clippedLayerInChild.move(0, 1); + clippedLayerInChild.move(0, 1); + EXPECT_FALSE(occlusion.occluded(child, clippedLayerInChild)); + EXPECT_FALSE(occlusion.unoccludedContentRect(child, clippedLayerInChild).isEmpty()); + clippedLayerInChild.move(0, -1); + + this->leaveContributingSurface(child, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(0u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(0u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_FALSE(occlusion.occluded(parent, IntRect(75, 55, 1, 1))); + EXPECT_INT_RECT_EQ(IntRect(75, 55, 1, 1), occlusion.unoccludedContentRect(parent, IntRect(75, 55, 1, 1))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestSurfaceRotatedOffAxis); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestSurfaceWithTwoOpaqueChildren : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix childTransform; + childTransform.translate(250, 250); + childTransform.rotate(90); + childTransform.translate(-250, -250); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::LayerType* child = this->createLayer(parent, childTransform, FloatPoint(30, 30), IntSize(500, 500)); + child->setMasksToBounds(true); + typename Types::ContentLayerType* layer1 = this->createDrawingLayer(child, this->identityMatrix, FloatPoint(10, 10), IntSize(500, 500), true); + typename Types::ContentLayerType* layer2 = this->createDrawingLayer(child, this->identityMatrix, FloatPoint(10, 450), IntSize(500, 60), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(layer2, occlusion); + this->visitLayer(layer1, occlusion); + this->enterContributingSurface(child, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 40, 70, 60), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(10, 430, 60, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(child, IntRect(10, 430, 60, 70))); + EXPECT_FALSE(occlusion.occluded(child, IntRect(9, 430, 60, 70))); + EXPECT_FALSE(occlusion.occluded(child, IntRect(10, 429, 60, 70))); + EXPECT_FALSE(occlusion.occluded(child, IntRect(11, 430, 60, 70))); + EXPECT_FALSE(occlusion.occluded(child, IntRect(10, 431, 60, 70))); + + EXPECT_TRUE(occlusion.unoccludedContentRect(child, IntRect(10, 430, 60, 70)).isEmpty()); + EXPECT_INT_RECT_EQ(IntRect(9, 430, 1, 70), occlusion.unoccludedContentRect(child, IntRect(9, 430, 60, 70))); + EXPECT_INT_RECT_EQ(IntRect(10, 429, 60, 1), occlusion.unoccludedContentRect(child, IntRect(10, 429, 60, 70))); + EXPECT_INT_RECT_EQ(IntRect(70, 430, 1, 70), occlusion.unoccludedContentRect(child, IntRect(11, 430, 60, 70))); + EXPECT_INT_RECT_EQ(IntRect(10, 500, 60, 1), occlusion.unoccludedContentRect(child, IntRect(10, 431, 60, 70))); + + this->leaveContributingSurface(child, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 40, 70, 60), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(30, 40, 70, 60), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 40, 70, 60))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(29, 40, 70, 60))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 39, 70, 60))); + + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(30, 40, 70, 60)).isEmpty()); + EXPECT_INT_RECT_EQ(IntRect(29, 40, 1, 60), occlusion.unoccludedContentRect(parent, IntRect(29, 40, 70, 60))); + EXPECT_INT_RECT_EQ(IntRect(30, 39, 70, 1), occlusion.unoccludedContentRect(parent, IntRect(30, 39, 70, 60))); + EXPECT_INT_RECT_EQ(IntRect(100, 40, 1, 60), occlusion.unoccludedContentRect(parent, IntRect(31, 40, 70, 60))); + EXPECT_INT_RECT_EQ(IntRect(30, 100, 70, 1), occlusion.unoccludedContentRect(parent, IntRect(30, 41, 70, 60))); + + /* Justification for the above occlusion from |layer1| and |layer2|: + + +---------------------+ + | |30 Visible region of |layer1|: ///// + | | Visible region of |layer2|: \\\\\ + | +---------------------------------+ + | | |10 | + | +---------------+-----------------+ | + | | |\\\\\\\\\\\\|//| 420 | | + | | |\\\\\\\\\\\\|//|60 | | + | | |\\\\\\\\\\\\|//| | | + +--|--|------------|--+ | | + 20|10| 70 | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | |10| + | +------------|-----------------|--+ + | | 490 | + +---------------+-----------------+ + 60 440 + */ + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestSurfaceWithTwoOpaqueChildren); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestOverlappingSurfaceSiblings : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix childTransform; + childTransform.translate(250, 250); + childTransform.rotate(90); + childTransform.translate(-250, -250); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::LayerType* child1 = this->createSurface(parent, childTransform, FloatPoint(30, 30), IntSize(10, 10)); + typename Types::LayerType* child2 = this->createSurface(parent, childTransform, FloatPoint(20, 40), IntSize(10, 10)); + typename Types::ContentLayerType* layer1 = this->createDrawingLayer(child1, this->identityMatrix, FloatPoint(-10, -10), IntSize(510, 510), true); + typename Types::ContentLayerType* layer2 = this->createDrawingLayer(child2, this->identityMatrix, FloatPoint(-10, -10), IntSize(510, 510), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(-20, -20, 1000, 1000)); + + this->visitLayer(layer2, occlusion); + this->enterContributingSurface(child2, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(20, 30, 80, 70), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(-10, 420, 70, 80), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(child2, IntRect(-10, 420, 70, 80))); + EXPECT_FALSE(occlusion.occluded(child2, IntRect(-11, 420, 70, 80))); + EXPECT_FALSE(occlusion.occluded(child2, IntRect(-10, 419, 70, 80))); + EXPECT_FALSE(occlusion.occluded(child2, IntRect(-10, 420, 71, 80))); + EXPECT_FALSE(occlusion.occluded(child2, IntRect(-10, 420, 70, 81))); + + occlusion.useDefaultLayerClipRect(); + EXPECT_TRUE(occlusion.occluded(child2, IntRect(-10, 420, 70, 80))); + EXPECT_TRUE(occlusion.occluded(child2, IntRect(-11, 420, 70, 80))); + EXPECT_TRUE(occlusion.occluded(child2, IntRect(-10, 419, 70, 80))); + EXPECT_TRUE(occlusion.occluded(child2, IntRect(-10, 420, 71, 80))); + EXPECT_TRUE(occlusion.occluded(child2, IntRect(-10, 420, 70, 81))); + occlusion.setLayerClipRect(IntRect(-20, -20, 1000, 1000)); + + // There is nothing above child2's surface in the z-order. + EXPECT_INT_RECT_EQ(IntRect(-10, 420, 70, 80), occlusion.unoccludedContributingSurfaceContentRect(child2, false, IntRect(-10, 420, 70, 80))); + + this->leaveContributingSurface(child2, occlusion); + this->visitLayer(layer1, occlusion); + this->enterContributingSurface(child1, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(20, 20, 80, 80), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(-10, 430, 80, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(child1, IntRect(-10, 430, 80, 70))); + EXPECT_FALSE(occlusion.occluded(child1, IntRect(-11, 430, 80, 70))); + EXPECT_FALSE(occlusion.occluded(child1, IntRect(-10, 429, 80, 70))); + EXPECT_FALSE(occlusion.occluded(child1, IntRect(-10, 430, 81, 70))); + EXPECT_FALSE(occlusion.occluded(child1, IntRect(-10, 430, 80, 71))); + + // child2's contents will occlude child1 below it. + EXPECT_INT_RECT_EQ(IntRect(-10, 430, 10, 70), occlusion.unoccludedContributingSurfaceContentRect(child1, false, IntRect(-10, 430, 80, 70))); + + this->leaveContributingSurface(child1, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(20, 20, 80, 80), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(20, 20, 80, 80), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_FALSE(occlusion.occluded(parent, IntRect(20, 20, 80, 80))); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(30, 20, 70, 80))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(29, 20, 70, 80))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(30, 19, 70, 80))); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(20, 30, 80, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(19, 30, 80, 70))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(20, 29, 80, 70))); + + /* Justification for the above occlusion: + 100 + +---------------------+ + | 20 | layer1 + | 30+ ---------------------------------+ + 100 | 30| | layer2 | + |20+----------------------------------+ | + | | | | | | + | | | | | | + | | | | | | + +--|-|----------------+ | | + | | | | 510 + | | | | + | | | | + | | | | + | | | | + | | | | + | | | | + | +--------------------------------|-+ + | | + +----------------------------------+ + 510 + */ + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestOverlappingSurfaceSiblings); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestOverlappingSurfaceSiblingsWithTwoTransforms : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix child1Transform; + child1Transform.translate(250, 250); + child1Transform.rotate(-90); + child1Transform.translate(-250, -250); + + WebTransformationMatrix child2Transform; + child2Transform.translate(250, 250); + child2Transform.rotate(90); + child2Transform.translate(-250, -250); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::LayerType* child1 = this->createSurface(parent, child1Transform, FloatPoint(30, 20), IntSize(10, 10)); + typename Types::LayerType* child2 = this->createDrawingSurface(parent, child2Transform, FloatPoint(20, 40), IntSize(10, 10), false); + typename Types::ContentLayerType* layer1 = this->createDrawingLayer(child1, this->identityMatrix, FloatPoint(-10, -20), IntSize(510, 510), true); + typename Types::ContentLayerType* layer2 = this->createDrawingLayer(child2, this->identityMatrix, FloatPoint(-10, -10), IntSize(510, 510), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(-30, -30, 1000, 1000)); + + this->visitLayer(layer2, occlusion); + this->enterLayer(child2, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(20, 30, 80, 70), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(-10, 420, 70, 80), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(child2, IntRect(-10, 420, 70, 80))); + EXPECT_FALSE(occlusion.occluded(child2, IntRect(-11, 420, 70, 80))); + EXPECT_FALSE(occlusion.occluded(child2, IntRect(-10, 419, 70, 80))); + EXPECT_FALSE(occlusion.occluded(child2, IntRect(-10, 420, 71, 80))); + EXPECT_FALSE(occlusion.occluded(child2, IntRect(-10, 420, 70, 81))); + + this->leaveLayer(child2, occlusion); + this->enterContributingSurface(child2, occlusion); + + // There is nothing above child2's surface in the z-order. + EXPECT_INT_RECT_EQ(IntRect(-10, 420, 70, 80), occlusion.unoccludedContributingSurfaceContentRect(child2, false, IntRect(-10, 420, 70, 80))); + + this->leaveContributingSurface(child2, occlusion); + this->visitLayer(layer1, occlusion); + this->enterContributingSurface(child1, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(10, 20, 90, 80), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(420, -20, 80, 90), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(child1, IntRect(420, -20, 80, 90))); + EXPECT_FALSE(occlusion.occluded(child1, IntRect(419, -20, 80, 90))); + EXPECT_FALSE(occlusion.occluded(child1, IntRect(420, -21, 80, 90))); + EXPECT_FALSE(occlusion.occluded(child1, IntRect(420, -19, 80, 90))); + EXPECT_FALSE(occlusion.occluded(child1, IntRect(421, -20, 80, 90))); + + // child2's contents will occlude child1 below it. + EXPECT_INT_RECT_EQ(IntRect(420, -20, 80, 90), occlusion.unoccludedContributingSurfaceContentRect(child1, false, IntRect(420, -20, 80, 90))); + EXPECT_INT_RECT_EQ(IntRect(490, -10, 10, 80), occlusion.unoccludedContributingSurfaceContentRect(child1, false, IntRect(420, -10, 80, 90))); + EXPECT_INT_RECT_EQ(IntRect(420, -20, 70, 10), occlusion.unoccludedContributingSurfaceContentRect(child1, false, IntRect(420, -20, 70, 90))); + + this->leaveContributingSurface(child1, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(10, 20, 90, 80), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(10, 20, 90, 80), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(10, 20, 90, 80))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(9, 20, 90, 80))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(10, 19, 90, 80))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(11, 20, 90, 80))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(10, 21, 90, 80))); + + /* Justification for the above occlusion: + 100 + +---------------------+ + |20 | layer1 + 10+----------------------------------+ + 100 || 30 | layer2 | + |20+----------------------------------+ + || | | | | + || | | | | + || | | | | + +|-|------------------+ | | + | | | | 510 + | | 510 | | + | | | | + | | | | + | | | | + | | | | + | | 520 | | + +----------------------------------+ | + | | + +----------------------------------+ + 510 + */ + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestOverlappingSurfaceSiblingsWithTwoTransforms); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestFilters : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix layerTransform; + layerTransform.translate(250, 250); + layerTransform.rotate(90); + layerTransform.translate(-250, -250); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::ContentLayerType* blurLayer = this->createDrawingLayer(parent, layerTransform, FloatPoint(30, 30), IntSize(500, 500), true); + typename Types::ContentLayerType* opaqueLayer = this->createDrawingLayer(parent, layerTransform, FloatPoint(30, 30), IntSize(500, 500), true); + typename Types::ContentLayerType* opacityLayer = this->createDrawingLayer(parent, layerTransform, FloatPoint(30, 30), IntSize(500, 500), true); + + WebFilterOperations filters; + filters.append(WebFilterOperation::createBlurFilter(10)); + blurLayer->setFilters(filters); + + filters.clear(); + filters.append(WebFilterOperation::createGrayscaleFilter(0.5)); + opaqueLayer->setFilters(filters); + + filters.clear(); + filters.append(WebFilterOperation::createOpacityFilter(0.5)); + opacityLayer->setFilters(filters); + + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + // Opacity layer won't contribute to occlusion. + this->visitLayer(opacityLayer, occlusion); + this->enterContributingSurface(opacityLayer, occlusion); + + EXPECT_TRUE(occlusion.occlusionInScreenSpace().isEmpty()); + EXPECT_TRUE(occlusion.occlusionInTargetSurface().isEmpty()); + + // And has nothing to contribute to its parent surface. + this->leaveContributingSurface(opacityLayer, occlusion); + EXPECT_TRUE(occlusion.occlusionInScreenSpace().isEmpty()); + EXPECT_TRUE(occlusion.occlusionInTargetSurface().isEmpty()); + + // Opaque layer will contribute to occlusion. + this->visitLayer(opaqueLayer, occlusion); + this->enterContributingSurface(opaqueLayer, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 430, 70, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + // And it gets translated to the parent surface. + this->leaveContributingSurface(opaqueLayer, occlusion); + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + // The blur layer needs to throw away any occlusion from outside its subtree. + this->enterLayer(blurLayer, occlusion); + EXPECT_TRUE(occlusion.occlusionInScreenSpace().isEmpty()); + EXPECT_TRUE(occlusion.occlusionInTargetSurface().isEmpty()); + + // And it won't contribute to occlusion. + this->leaveLayer(blurLayer, occlusion); + this->enterContributingSurface(blurLayer, occlusion); + EXPECT_TRUE(occlusion.occlusionInScreenSpace().isEmpty()); + EXPECT_TRUE(occlusion.occlusionInTargetSurface().isEmpty()); + + // But the opaque layer's occlusion is preserved on the parent. + this->leaveContributingSurface(blurLayer, occlusion); + this->enterLayer(parent, occlusion); + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(30, 30, 70, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestFilters); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestReplicaDoesOcclude : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 200)); + typename Types::LayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 100), IntSize(50, 50), true); + this->createReplicaLayer(surface, this->identityMatrix, FloatPoint(50, 50), IntSize()); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(surface, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 100, 50, 50), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 50, 50), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->visitContributingSurface(surface, occlusion); + this->enterLayer(parent, occlusion); + + // The surface and replica should both be occluding the parent. + EXPECT_INT_RECT_EQ(IntRect(0, 100, 100, 100), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInTargetSurface().rects().size()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestReplicaDoesOcclude); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestReplicaWithClipping : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 170)); + typename Types::LayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 100), IntSize(50, 50), true); + this->createReplicaLayer(surface, this->identityMatrix, FloatPoint(50, 50), IntSize()); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(surface, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 100, 50, 50), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 50, 50), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->visitContributingSurface(surface, occlusion); + this->enterLayer(parent, occlusion); + + // The surface and replica should both be occluding the parent. + EXPECT_INT_RECT_EQ(IntRect(0, 100, 100, 70), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInTargetSurface().rects().size()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestReplicaWithClipping); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestReplicaWithMask : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 200)); + typename Types::LayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 100), IntSize(50, 50), true); + typename Types::LayerType* replica = this->createReplicaLayer(surface, this->identityMatrix, FloatPoint(50, 50), IntSize()); + this->createMaskLayer(replica, IntSize(10, 10)); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(surface, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 100, 50, 50), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 50, 50), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->visitContributingSurface(surface, occlusion); + this->enterLayer(parent, occlusion); + + // The replica should not be occluding the parent, since it has a mask applied to it. + EXPECT_INT_RECT_EQ(IntRect(0, 100, 50, 50), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestReplicaWithMask); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestLayerClipRectOutsideChild : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 200), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(200, 100, 100, 100)); + + this->enterLayer(layer, occlusion); + + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(100, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(200, 100, 100, 100))); + + occlusion.useDefaultLayerClipRect(); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(200, 100, 100, 100))); + occlusion.setLayerClipRect(IntRect(200, 100, 100, 100)); + + this->leaveLayer(layer, occlusion); + this->visitContributingSurface(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(200, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + + EXPECT_INT_RECT_EQ(IntRect(200, 100, 100, 100), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestLayerClipRectOutsideChild); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestViewportRectOutsideChild : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 200), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(200, 100, 100, 100)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->enterLayer(layer, occlusion); + + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(100, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(200, 100, 100, 100))); + + occlusion.useDefaultLayerClipRect(); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(200, 100, 100, 100))); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->leaveLayer(layer, occlusion); + this->visitContributingSurface(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(200, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + + EXPECT_INT_RECT_EQ(IntRect(200, 100, 100, 100), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestViewportRectOutsideChild); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestLayerClipRectOverChild : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 200), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(100, 100, 100, 100)); + + this->enterLayer(layer, occlusion); + + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(100, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(100, 100, 100, 100))); + + this->leaveLayer(layer, occlusion); + this->visitContributingSurface(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300)).isEmpty()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestLayerClipRectOverChild); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestViewportRectOverChild : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 200), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(100, 100, 100, 100)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->enterLayer(layer, occlusion); + + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(100, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(100, 100, 100, 100))); + + this->leaveLayer(layer, occlusion); + this->visitContributingSurface(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300)).isEmpty()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestViewportRectOverChild); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestLayerClipRectPartlyOverChild : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 200), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(50, 50, 200, 200)); + + this->enterLayer(layer, occlusion); + + EXPECT_FALSE(occlusion.occluded(layer, IntRect(0, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(0, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(100, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(100, 100, 100, 100))); + + this->leaveLayer(layer, occlusion); + this->visitContributingSurface(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(200, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(200, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(0, 200, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(100, 200, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + + EXPECT_INT_RECT_EQ(IntRect(50, 50, 200, 200), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300))); + EXPECT_INT_RECT_EQ(IntRect(200, 50, 50, 50), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 100))); + EXPECT_INT_RECT_EQ(IntRect(200, 100, 50, 100), occlusion.unoccludedContentRect(parent, IntRect(0, 100, 300, 100))); + EXPECT_INT_RECT_EQ(IntRect(200, 100, 50, 100), occlusion.unoccludedContentRect(parent, IntRect(200, 100, 100, 100))); + EXPECT_INT_RECT_EQ(IntRect(100, 200, 100, 50), occlusion.unoccludedContentRect(parent, IntRect(100, 200, 100, 100))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestLayerClipRectPartlyOverChild); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestViewportRectPartlyOverChild : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 200), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(50, 50, 200, 200)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->enterLayer(layer, occlusion); + + EXPECT_FALSE(occlusion.occluded(layer, IntRect(0, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(0, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(100, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(100, 100, 100, 100))); + + this->leaveLayer(layer, occlusion); + this->visitContributingSurface(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(200, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(200, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(0, 200, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(100, 200, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + + EXPECT_INT_RECT_EQ(IntRect(50, 50, 200, 200), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300))); + EXPECT_INT_RECT_EQ(IntRect(200, 50, 50, 50), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 100))); + EXPECT_INT_RECT_EQ(IntRect(200, 100, 50, 100), occlusion.unoccludedContentRect(parent, IntRect(0, 100, 300, 100))); + EXPECT_INT_RECT_EQ(IntRect(200, 100, 50, 100), occlusion.unoccludedContentRect(parent, IntRect(200, 100, 100, 100))); + EXPECT_INT_RECT_EQ(IntRect(100, 200, 100, 50), occlusion.unoccludedContentRect(parent, IntRect(100, 200, 100, 100))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestViewportRectPartlyOverChild); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestLayerClipRectOverNothing : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 200), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(500, 500, 100, 100)); + + this->enterLayer(layer, occlusion); + + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(100, 100, 100, 100))); + + this->leaveLayer(layer, occlusion); + this->visitContributingSurface(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300)).isEmpty()); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 100)).isEmpty()); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(0, 100, 300, 100)).isEmpty()); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(200, 100, 100, 100)).isEmpty()); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(100, 200, 100, 100)).isEmpty()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestLayerClipRectOverNothing); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestViewportRectOverNothing : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 200), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(500, 500, 100, 100)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->enterLayer(layer, occlusion); + + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(layer, IntRect(100, 100, 100, 100))); + + this->leaveLayer(layer, occlusion); + this->visitContributingSurface(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 0, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(0, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 200, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300)).isEmpty()); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 100)).isEmpty()); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(0, 100, 300, 100)).isEmpty()); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(200, 100, 100, 100)).isEmpty()); + EXPECT_TRUE(occlusion.unoccludedContentRect(parent, IntRect(100, 200, 100, 100)).isEmpty()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestViewportRectOverNothing); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestLayerClipRectForLayerOffOrigin : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 200), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + this->enterLayer(layer, occlusion); + + // This layer is translated when drawn into its target. So if the clip rect given from the target surface + // is not in that target space, then after translating these query rects into the target, they will fall outside + // the clip and be considered occluded. + EXPECT_FALSE(occlusion.occluded(layer, IntRect(0, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(0, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(100, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(100, 100, 100, 100))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestLayerClipRectForLayerOffOrigin); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestOpaqueContentsRegionEmpty : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 200), false); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + this->enterLayer(layer, occlusion); + + EXPECT_FALSE(occlusion.occluded(layer, IntRect(0, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(100, 0, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(0, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(100, 100, 100, 100))); + + // Occluded since its outside the surface bounds. + EXPECT_TRUE(occlusion.occluded(layer, IntRect(200, 100, 100, 100))); + + // Test without any clip rect. + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + EXPECT_FALSE(occlusion.occluded(layer, IntRect(200, 100, 100, 100))); + occlusion.useDefaultLayerClipRect(); + + this->leaveLayer(layer, occlusion); + this->visitContributingSurface(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_TRUE(occlusion.occlusionInScreenSpace().bounds().isEmpty()); + EXPECT_EQ(0u, occlusion.occlusionInScreenSpace().rects().size()); + } +}; + +MAIN_AND_IMPL_THREAD_TEST(CCOcclusionTrackerTestOpaqueContentsRegionEmpty); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestOpaqueContentsRegionNonEmpty : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(100, 100), IntSize(200, 200), false); + this->calcDrawEtc(parent); + + { + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + layer->setOpaqueContentsRect(IntRect(0, 0, 100, 100)); + + this->resetLayerIterator(); + this->visitLayer(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(100, 100, 100, 100), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + + EXPECT_FALSE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(100, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + } + + { + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + layer->setOpaqueContentsRect(IntRect(20, 20, 180, 180)); + + this->resetLayerIterator(); + this->visitLayer(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(120, 120, 180, 180), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + + EXPECT_FALSE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(100, 100, 100, 100))); + EXPECT_TRUE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + } + + { + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + layer->setOpaqueContentsRect(IntRect(150, 150, 100, 100)); + + this->resetLayerIterator(); + this->visitLayer(layer, occlusion); + this->enterLayer(parent, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(250, 250, 50, 50), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + + EXPECT_FALSE(occlusion.occluded(parent, IntRect(0, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(100, 100, 100, 100))); + EXPECT_FALSE(occlusion.occluded(parent, IntRect(200, 200, 100, 100))); + } + } +}; + +MAIN_AND_IMPL_THREAD_TEST(CCOcclusionTrackerTestOpaqueContentsRegionNonEmpty); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTest3dTransform : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix transform; + transform.rotate3d(0, 30, 0); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::LayerType* container = this->createLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(container, transform, FloatPoint(100, 100), IntSize(200, 200), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + this->enterLayer(layer, occlusion); + + // The layer is rotated in 3d but without preserving 3d, so it only gets resized. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 200, 200), occlusion.unoccludedContentRect(layer, IntRect(0, 0, 200, 200))); + } +}; + +MAIN_AND_IMPL_THREAD_TEST(CCOcclusionTrackerTest3dTransform); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestUnsorted3dLayers : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + // Currently, the main thread layer iterator does not iterate over 3d items in + // sorted order, because layer sorting is not performed on the main thread. + // Because of this, the occlusion tracker cannot assume that a 3d layer occludes + // other layers that have not yet been iterated over. For now, the expected + // behavior is that a 3d layer simply does not add any occlusion to the occlusion + // tracker. + + WebTransformationMatrix translationToFront; + translationToFront.translate3d(0, 0, -10); + WebTransformationMatrix translationToBack; + translationToFront.translate3d(0, 0, -100); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* child1 = this->createDrawingLayer(parent, translationToBack, FloatPoint(0, 0), IntSize(100, 100), true); + typename Types::ContentLayerType* child2 = this->createDrawingLayer(parent, translationToFront, FloatPoint(50, 50), IntSize(100, 100), true); + parent->setPreserves3D(true); + + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + this->visitLayer(child2, occlusion); + EXPECT_TRUE(occlusion.occlusionInScreenSpace().isEmpty()); + EXPECT_TRUE(occlusion.occlusionInTargetSurface().isEmpty()); + + this->visitLayer(child1, occlusion); + EXPECT_TRUE(occlusion.occlusionInScreenSpace().isEmpty()); + EXPECT_TRUE(occlusion.occlusionInTargetSurface().isEmpty()); + } +}; + +// This test will have different layer ordering on the impl thread; the test will only work on the main thread. +MAIN_THREAD_TEST(CCOcclusionTrackerTestUnsorted3dLayers); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestPerspectiveTransform : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix transform; + transform.translate(150, 150); + transform.applyPerspective(400); + transform.rotate3d(1, 0, 0, -30); + transform.translate(-150, -150); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::LayerType* container = this->createLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(container, transform, FloatPoint(100, 100), IntSize(200, 200), true); + container->setPreserves3D(true); + layer->setPreserves3D(true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + this->enterLayer(layer, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 200, 200), occlusion.unoccludedContentRect(layer, IntRect(0, 0, 200, 200))); + } +}; + +// This test requires accumulating occlusion of 3d layers, which are skipped by the occlusion tracker on the main thread. So this test should run on the impl thread. +IMPL_THREAD_TEST(CCOcclusionTrackerTestPerspectiveTransform); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestPerspectiveTransformBehindCamera : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + // This test is based on the platform/chromium/compositing/3d-corners.html layout test. + WebTransformationMatrix transform; + transform.translate(250, 50); + transform.applyPerspective(10); + transform.translate(-250, -50); + transform.translate(250, 50); + transform.rotate3d(1, 0, 0, -167); + transform.translate(-250, -50); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(500, 100)); + typename Types::LayerType* container = this->createLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(500, 500)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(container, transform, FloatPoint(0, 0), IntSize(500, 500), true); + container->setPreserves3D(true); + layer->setPreserves3D(true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + this->enterLayer(layer, occlusion); + + // The bottom 11 pixel rows of this layer remain visible inside the container, after translation to the target surface. When translated back, + // this will include many more pixels but must include at least the bottom 11 rows. + EXPECT_TRUE(occlusion.unoccludedContentRect(layer, IntRect(0, 0, 500, 500)).contains(IntRect(0, 489, 500, 11))); + } +}; + +// This test requires accumulating occlusion of 3d layers, which are skipped by the occlusion tracker on the main thread. So this test should run on the impl thread. +IMPL_THREAD_TEST(CCOcclusionTrackerTestPerspectiveTransformBehindCamera); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestLayerBehindCameraDoesNotOcclude : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix transform; + transform.translate(50, 50); + transform.applyPerspective(100); + transform.translate3d(0, 0, 110); + transform.translate(-50, -50); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(parent, transform, FloatPoint(0, 0), IntSize(100, 100), true); + parent->setPreserves3D(true); + layer->setPreserves3D(true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + + // The |layer| is entirely behind the camera and should not occlude. + this->visitLayer(layer, occlusion); + this->enterLayer(parent, occlusion); + EXPECT_EQ(0u, occlusion.occlusionInTargetSurface().rects().size()); + EXPECT_EQ(0u, occlusion.occlusionInScreenSpace().rects().size()); + } +}; + +// This test requires accumulating occlusion of 3d layers, which are skipped by the occlusion tracker on the main thread. So this test should run on the impl thread. +IMPL_THREAD_TEST(CCOcclusionTrackerTestLayerBehindCameraDoesNotOcclude); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestLargePixelsOccludeInsideClipRect : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix transform; + transform.translate(50, 50); + transform.applyPerspective(100); + transform.translate3d(0, 0, 99); + transform.translate(-50, -50); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(parent, transform, FloatPoint(0, 0), IntSize(100, 100), true); + parent->setPreserves3D(true); + layer->setPreserves3D(true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + + // This is very close to the camera, so pixels in its visibleContentRect will actually go outside of the layer's clipRect. + // Ensure that those pixels don't occlude things outside the clipRect. + this->visitLayer(layer, occlusion); + this->enterLayer(parent, occlusion); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 100), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 100), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + } +}; + +// This test requires accumulating occlusion of 3d layers, which are skipped by the occlusion tracker on the main thread. So this test should run on the impl thread. +IMPL_THREAD_TEST(CCOcclusionTrackerTestLargePixelsOccludeInsideClipRect); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestAnimationOpacity1OnMainThread : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300), true); + typename Types::ContentLayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300), true); + typename Types::ContentLayerType* surfaceChild = this->createDrawingLayer(surface, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 300), true); + typename Types::ContentLayerType* surfaceChild2 = this->createDrawingLayer(surface, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 300), true); + typename Types::ContentLayerType* parent2 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300), false); + typename Types::ContentLayerType* topmost = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(250, 0), IntSize(50, 300), true); + + addOpacityTransitionToController(*layer->layerAnimationController(), 10, 0, 1, false); + addOpacityTransitionToController(*surface->layerAnimationController(), 10, 0, 1, false); + this->calcDrawEtc(parent); + + EXPECT_TRUE(layer->drawOpacityIsAnimating()); + EXPECT_FALSE(surface->drawOpacityIsAnimating()); + EXPECT_TRUE(surface->renderSurface()->drawOpacityIsAnimating()); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(topmost, occlusion); + this->enterLayer(parent2, occlusion); + // This occlusion will affect all surfaces. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 250, 300), occlusion.unoccludedContentRect(parent2, IntRect(0, 0, 300, 300))); + this->leaveLayer(parent2, occlusion); + + this->visitLayer(surfaceChild2, occlusion); + this->enterLayer(surfaceChild, occlusion); + EXPECT_INT_RECT_EQ(IntRect(100, 0, 100, 300), occlusion.unoccludedContentRect(surfaceChild, IntRect(0, 0, 300, 300))); + this->leaveLayer(surfaceChild, occlusion); + this->enterLayer(surface, occlusion); + EXPECT_INT_RECT_EQ(IntRect(200, 0, 50, 300), occlusion.unoccludedContentRect(surface, IntRect(0, 0, 300, 300))); + this->leaveLayer(surface, occlusion); + + this->enterContributingSurface(surface, occlusion); + // Occlusion within the surface is lost when leaving the animating surface. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 250, 300), occlusion.unoccludedContributingSurfaceContentRect(surface, false, IntRect(0, 0, 300, 300))); + this->leaveContributingSurface(surface, occlusion); + + this->visitLayer(layer, occlusion); + this->enterLayer(parent, occlusion); + + // Occlusion is not added for the animating |layer|. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 250, 300), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300))); + } +}; + +MAIN_THREAD_TEST(CCOcclusionTrackerTestAnimationOpacity1OnMainThread); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestAnimationOpacity0OnMainThread : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300), true); + typename Types::ContentLayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300), true); + typename Types::ContentLayerType* surfaceChild = this->createDrawingLayer(surface, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 300), true); + typename Types::ContentLayerType* surfaceChild2 = this->createDrawingLayer(surface, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 300), true); + typename Types::ContentLayerType* parent2 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300), false); + typename Types::ContentLayerType* topmost = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(250, 0), IntSize(50, 300), true); + + addOpacityTransitionToController(*layer->layerAnimationController(), 10, 1, 0, false); + addOpacityTransitionToController(*surface->layerAnimationController(), 10, 1, 0, false); + this->calcDrawEtc(parent); + + EXPECT_TRUE(layer->drawOpacityIsAnimating()); + EXPECT_FALSE(surface->drawOpacityIsAnimating()); + EXPECT_TRUE(surface->renderSurface()->drawOpacityIsAnimating()); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(topmost, occlusion); + this->enterLayer(parent2, occlusion); + // This occlusion will affect all surfaces. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 250, 300), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300))); + this->leaveLayer(parent2, occlusion); + + this->visitLayer(surfaceChild2, occlusion); + this->enterLayer(surfaceChild, occlusion); + EXPECT_INT_RECT_EQ(IntRect(100, 0, 100, 300), occlusion.unoccludedContentRect(surfaceChild, IntRect(0, 0, 300, 300))); + this->leaveLayer(surfaceChild, occlusion); + this->enterLayer(surface, occlusion); + EXPECT_INT_RECT_EQ(IntRect(200, 0, 50, 300), occlusion.unoccludedContentRect(surface, IntRect(0, 0, 300, 300))); + this->leaveLayer(surface, occlusion); + + this->enterContributingSurface(surface, occlusion); + // Occlusion within the surface is lost when leaving the animating surface. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 250, 300), occlusion.unoccludedContributingSurfaceContentRect(surface, false, IntRect(0, 0, 300, 300))); + this->leaveContributingSurface(surface, occlusion); + + this->visitLayer(layer, occlusion); + this->enterLayer(parent, occlusion); + + // Occlusion is not added for the animating |layer|. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 250, 300), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300))); + } +}; + +MAIN_THREAD_TEST(CCOcclusionTrackerTestAnimationOpacity0OnMainThread); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestAnimationTranslateOnMainThread : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* layer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300), true); + typename Types::ContentLayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300), true); + typename Types::ContentLayerType* surfaceChild = this->createDrawingLayer(surface, this->identityMatrix, FloatPoint(0, 0), IntSize(200, 300), true); + typename Types::ContentLayerType* surfaceChild2 = this->createDrawingLayer(surface, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 300), true); + typename Types::ContentLayerType* surface2 = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(50, 300), true); + + addAnimatedTransformToController(*layer->layerAnimationController(), 10, 30, 0); + addAnimatedTransformToController(*surface->layerAnimationController(), 10, 30, 0); + addAnimatedTransformToController(*surfaceChild->layerAnimationController(), 10, 30, 0); + this->calcDrawEtc(parent); + + EXPECT_TRUE(layer->drawTransformIsAnimating()); + EXPECT_TRUE(layer->screenSpaceTransformIsAnimating()); + EXPECT_TRUE(surface->renderSurface()->targetSurfaceTransformsAreAnimating()); + EXPECT_TRUE(surface->renderSurface()->screenSpaceTransformsAreAnimating()); + // The surface owning layer doesn't animate against its own surface. + EXPECT_FALSE(surface->drawTransformIsAnimating()); + EXPECT_TRUE(surface->screenSpaceTransformIsAnimating()); + EXPECT_TRUE(surfaceChild->drawTransformIsAnimating()); + EXPECT_TRUE(surfaceChild->screenSpaceTransformIsAnimating()); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(surface2, occlusion); + this->enterContributingSurface(surface2, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 50, 300), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + + this->leaveContributingSurface(surface2, occlusion); + this->enterLayer(surfaceChild2, occlusion); + + // surfaceChild2 is moving in screen space but not relative to its target, so occlusion should happen in its target space only. + // It also means that things occluding in screen space (e.g. surface2) cannot occlude this layer. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 300), occlusion.unoccludedContentRect(surfaceChild2, IntRect(0, 0, 100, 300))); + EXPECT_FALSE(occlusion.occluded(surfaceChild, IntRect(0, 0, 50, 300))); + + this->leaveLayer(surfaceChild2, occlusion); + this->enterLayer(surfaceChild, occlusion); + EXPECT_FALSE(occlusion.occluded(surfaceChild, IntRect(0, 0, 100, 300))); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 50, 300), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 300), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(100, 0, 200, 300), occlusion.unoccludedContentRect(surface, IntRect(0, 0, 300, 300))); + + // The surfaceChild is occluded by the surfaceChild2, but is moving relative its target and the screen, so it + // can't be occluded. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 200, 300), occlusion.unoccludedContentRect(surfaceChild, IntRect(0, 0, 200, 300))); + EXPECT_FALSE(occlusion.occluded(surfaceChild, IntRect(0, 0, 50, 300))); + + this->leaveLayer(surfaceChild, occlusion); + this->enterLayer(surface, occlusion); + // The surfaceChild is moving in screen space but not relative to its target, so occlusion should happen in its target space only. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 50, 300), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 300), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(100, 0, 200, 300), occlusion.unoccludedContentRect(surface, IntRect(0, 0, 300, 300))); + + this->leaveLayer(surface, occlusion); + // The surface's owning layer is moving in screen space but not relative to its target, so occlusion should happen in its target space only. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 50, 300), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 300), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 0, 0), occlusion.unoccludedContentRect(surface, IntRect(0, 0, 300, 300))); + + this->enterContributingSurface(surface, occlusion); + // The contributing |surface| is animating so it can't be occluded. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 300), occlusion.unoccludedContributingSurfaceContentRect(surface, false, IntRect(0, 0, 300, 300))); + this->leaveContributingSurface(surface, occlusion); + + this->enterLayer(layer, occlusion); + // The |surface| is moving in the screen and in its target, so all occlusion within the surface is lost when leaving it. + EXPECT_INT_RECT_EQ(IntRect(50, 0, 250, 300), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300))); + this->leaveLayer(layer, occlusion); + + this->enterLayer(parent, occlusion); + // The |layer| is animating in the screen and in its target, so no occlusion is added. + EXPECT_INT_RECT_EQ(IntRect(50, 0, 250, 300), occlusion.unoccludedContentRect(parent, IntRect(0, 0, 300, 300))); + } +}; + +MAIN_THREAD_TEST(CCOcclusionTrackerTestAnimationTranslateOnMainThread); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestSurfaceOcclusionTranslatesToParent : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix surfaceTransform; + surfaceTransform.translate(300, 300); + surfaceTransform.scale(2); + surfaceTransform.translate(-150, -150); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(500, 500)); + typename Types::ContentLayerType* surface = this->createDrawingSurface(parent, surfaceTransform, FloatPoint(0, 0), IntSize(300, 300), false); + typename Types::ContentLayerType* surface2 = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(50, 50), IntSize(300, 300), false); + surface->setOpaqueContentsRect(IntRect(0, 0, 200, 200)); + surface2->setOpaqueContentsRect(IntRect(0, 0, 200, 200)); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(surface2, occlusion); + this->visitContributingSurface(surface2, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(50, 50, 200, 200), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(50, 50, 200, 200), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + // Clear any stored occlusion. + occlusion.setOcclusionInScreenSpace(Region()); + occlusion.setOcclusionInTargetSurface(Region()); + + this->visitLayer(surface, occlusion); + this->visitContributingSurface(surface, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 400, 400), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 400, 400), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + } +}; + +MAIN_AND_IMPL_THREAD_TEST(CCOcclusionTrackerTestSurfaceOcclusionTranslatesToParent); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestSurfaceOcclusionTranslatesWithClipping : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 300)); + typename Types::ContentLayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(500, 300), false); + surface->setOpaqueContentsRect(IntRect(0, 0, 400, 200)); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(surface, occlusion); + this->visitContributingSurface(surface, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 200), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 200), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + } +}; + +MAIN_AND_IMPL_THREAD_TEST(CCOcclusionTrackerTestSurfaceOcclusionTranslatesWithClipping); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestReplicaOccluded : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 200)); + typename Types::LayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100), true); + this->createReplicaLayer(surface, this->identityMatrix, FloatPoint(0, 100), IntSize(100, 100)); + typename Types::LayerType* topmost = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 100), IntSize(100, 100), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + // |topmost| occludes the replica, but not the surface itself. + this->visitLayer(topmost, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 100, 100, 100), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 100, 100, 100), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->visitLayer(surface, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 200), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 100), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->enterContributingSurface(surface, occlusion); + + // Surface is not occluded so it shouldn't think it is. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 100), occlusion.unoccludedContributingSurfaceContentRect(surface, false, IntRect(0, 0, 100, 100))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestReplicaOccluded); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestSurfaceWithReplicaUnoccluded : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 200)); + typename Types::LayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100), true); + this->createReplicaLayer(surface, this->identityMatrix, FloatPoint(0, 100), IntSize(100, 100)); + typename Types::LayerType* topmost = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 110), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + // |topmost| occludes the surface, but not the entire surface's replica. + this->visitLayer(topmost, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 110), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 110), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->visitLayer(surface, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 110), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 100), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->enterContributingSurface(surface, occlusion); + + // Surface is occluded, but only the top 10px of the replica. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 0, 0), occlusion.unoccludedContributingSurfaceContentRect(surface, false, IntRect(0, 0, 100, 100))); + EXPECT_INT_RECT_EQ(IntRect(0, 10, 100, 90), occlusion.unoccludedContributingSurfaceContentRect(surface, true, IntRect(0, 0, 100, 100))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestSurfaceWithReplicaUnoccluded); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestSurfaceAndReplicaOccludedDifferently : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 200)); + typename Types::LayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100), true); + this->createReplicaLayer(surface, this->identityMatrix, FloatPoint(0, 100), IntSize(100, 100)); + typename Types::LayerType* overSurface = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(40, 100), true); + typename Types::LayerType* overReplica = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 100), IntSize(50, 100), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + // These occlude the surface and replica differently, so we can test each one. + this->visitLayer(overReplica, occlusion); + this->visitLayer(overSurface, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 50, 200), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 50, 200), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInTargetSurface().rects().size()); + + this->visitLayer(surface, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 200), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 100), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->enterContributingSurface(surface, occlusion); + + // Surface and replica are occluded different amounts. + EXPECT_INT_RECT_EQ(IntRect(40, 0, 60, 100), occlusion.unoccludedContributingSurfaceContentRect(surface, false, IntRect(0, 0, 100, 100))); + EXPECT_INT_RECT_EQ(IntRect(50, 0, 50, 100), occlusion.unoccludedContributingSurfaceContentRect(surface, true, IntRect(0, 0, 100, 100))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestSurfaceAndReplicaOccludedDifferently); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestSurfaceChildOfSurface : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + // This test verifies that the surface cliprect does not end up empty and clip away the entire unoccluded rect. + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 200)); + typename Types::LayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100), true); + typename Types::LayerType* surfaceChild = this->createDrawingSurface(surface, this->identityMatrix, FloatPoint(0, 10), IntSize(100, 50), true); + typename Types::LayerType* topmost = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 50), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(-100, -100, 1000, 1000)); + + // |topmost| occludes everything partially so we know occlusion is happening at all. + this->visitLayer(topmost, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 50), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 50), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->visitLayer(surfaceChild, occlusion); + + // surfaceChild increases the occlusion in the screen by a narrow sliver. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 60), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + // In its own surface, surfaceChild is at 0,0 as is its occlusion. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 50), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + // The root layer always has a clipRect. So the parent of |surface| has a clipRect. However, the owning layer for |surface| does not + // mask to bounds, so it doesn't have a clipRect of its own. Thus the parent of |surfaceChild| exercises different code paths + // as its parent does not have a clipRect. + + this->enterContributingSurface(surfaceChild, occlusion); + // The surfaceChild's parent does not have a clipRect as it owns a render surface. Make sure the unoccluded rect + // does not get clipped away inappropriately. + EXPECT_INT_RECT_EQ(IntRect(0, 40, 100, 10), occlusion.unoccludedContributingSurfaceContentRect(surfaceChild, false, IntRect(0, 0, 100, 50))); + this->leaveContributingSurface(surfaceChild, occlusion); + + // When the surfaceChild's occlusion is transformed up to its parent, make sure it is not clipped away inappropriately also. + this->enterLayer(surface, occlusion); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 60), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 10, 100, 50), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + this->leaveLayer(surface, occlusion); + + this->enterContributingSurface(surface, occlusion); + // The surface's parent does have a clipRect as it is the root layer. + EXPECT_INT_RECT_EQ(IntRect(0, 50, 100, 50), occlusion.unoccludedContributingSurfaceContentRect(surface, false, IntRect(0, 0, 100, 100))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestSurfaceChildOfSurface); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestTopmostSurfaceIsClippedToViewport : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + // This test verifies that the top-most surface is considered occluded outside of its target's clipRect and outside the viewport rect. + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(100, 200)); + typename Types::LayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 300), true); + this->calcDrawEtc(parent); + + { + // Make a viewport rect that is larger than the root layer. + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(surface, occlusion); + + // The root layer always has a clipRect. So the parent of |surface| has a clipRect giving the surface itself a clipRect. + this->enterContributingSurface(surface, occlusion); + // Make sure the parent's clipRect clips the unoccluded region of the child surface. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 200), occlusion.unoccludedContributingSurfaceContentRect(surface, false, IntRect(0, 0, 100, 300))); + } + this->resetLayerIterator(); + { + // Make a viewport rect that is smaller than the root layer. + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 100, 100)); + + this->visitLayer(surface, occlusion); + + // The root layer always has a clipRect. So the parent of |surface| has a clipRect giving the surface itself a clipRect. + this->enterContributingSurface(surface, occlusion); + // Make sure the viewport rect clips the unoccluded region of the child surface. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 100), occlusion.unoccludedContributingSurfaceContentRect(surface, false, IntRect(0, 0, 100, 300))); + } + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestTopmostSurfaceIsClippedToViewport); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestSurfaceChildOfClippingSurface : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + // This test verifies that the surface cliprect does not end up empty and clip away the entire unoccluded rect. + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(80, 200)); + typename Types::LayerType* surface = this->createDrawingSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100), true); + typename Types::LayerType* surfaceChild = this->createDrawingSurface(surface, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 100), false); + typename Types::LayerType* topmost = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(100, 50), true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + // |topmost| occludes everything partially so we know occlusion is happening at all. + this->visitLayer(topmost, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 80, 50), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 80, 50), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + // surfaceChild is not opaque and does not occlude, so we have a non-empty unoccluded area on surface. + this->visitLayer(surfaceChild, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 80, 50), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 0, 0), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(0u, occlusion.occlusionInTargetSurface().rects().size()); + + // The root layer always has a clipRect. So the parent of |surface| has a clipRect. However, the owning layer for |surface| does not + // mask to bounds, so it doesn't have a clipRect of its own. Thus the parent of |surfaceChild| exercises different code paths + // as its parent does not have a clipRect. + + this->enterContributingSurface(surfaceChild, occlusion); + // The surfaceChild's parent does not have a clipRect as it owns a render surface. + EXPECT_INT_RECT_EQ(IntRect(0, 50, 80, 50), occlusion.unoccludedContributingSurfaceContentRect(surfaceChild, false, IntRect(0, 0, 100, 100))); + this->leaveContributingSurface(surfaceChild, occlusion); + + this->visitLayer(surface, occlusion); + this->enterContributingSurface(surface, occlusion); + // The surface's parent does have a clipRect as it is the root layer. + EXPECT_INT_RECT_EQ(IntRect(0, 50, 80, 50), occlusion.unoccludedContributingSurfaceContentRect(surface, false, IntRect(0, 0, 100, 100))); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestSurfaceChildOfClippingSurface); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestDontOccludePixelsNeededForBackgroundFilter : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix scaleByHalf; + scaleByHalf.scale(0.5); + + // Make a surface and its replica, each 50x50, that are completely surrounded by opaque layers which are above them in the z-order. + // The surface is scaled to test that the pixel moving is done in the target space, where the background filter is applied, but the surface + // appears at 50, 50 and the replica at 200, 50. + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 150)); + typename Types::LayerType* filteredSurface = this->createDrawingLayer(parent, scaleByHalf, FloatPoint(50, 50), IntSize(100, 100), false); + this->createReplicaLayer(filteredSurface, this->identityMatrix, FloatPoint(300, 0), IntSize()); + typename Types::LayerType* occludingLayer1 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 50), true); + typename Types::LayerType* occludingLayer2 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 100), IntSize(300, 50), true); + typename Types::LayerType* occludingLayer3 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 50), IntSize(50, 50), true); + typename Types::LayerType* occludingLayer4 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(100, 50), IntSize(100, 50), true); + typename Types::LayerType* occludingLayer5 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(250, 50), IntSize(50, 50), true); + + // Filters make the layer own a surface. + WebFilterOperations filters; + filters.append(WebFilterOperation::createBlurFilter(10)); + filteredSurface->setBackgroundFilters(filters); + + // Save the distance of influence for the blur effect. + int outsetTop, outsetRight, outsetBottom, outsetLeft; + filters.getOutsets(outsetTop, outsetRight, outsetBottom, outsetLeft); + + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + // These layers occlude pixels directly beside the filteredSurface. Because filtered surface blends pixels in a radius, it will + // need to see some of the pixels (up to radius far) underneath the occludingLayers. + this->visitLayer(occludingLayer5, occlusion); + this->visitLayer(occludingLayer4, occlusion); + this->visitLayer(occludingLayer3, occlusion); + this->visitLayer(occludingLayer2, occlusion); + this->visitLayer(occludingLayer1, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 150), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(5u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 150), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(5u, occlusion.occlusionInTargetSurface().rects().size()); + + // Everything outside the surface/replica is occluded but the surface/replica itself is not. + this->enterLayer(filteredSurface, occlusion); + EXPECT_INT_RECT_EQ(IntRect(1, 0, 99, 100), occlusion.unoccludedContentRect(filteredSurface, IntRect(1, 0, 100, 100))); + EXPECT_INT_RECT_EQ(IntRect(0, 1, 100, 99), occlusion.unoccludedContentRect(filteredSurface, IntRect(0, 1, 100, 100))); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 99, 100), occlusion.unoccludedContentRect(filteredSurface, IntRect(-1, 0, 100, 100))); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 100, 99), occlusion.unoccludedContentRect(filteredSurface, IntRect(0, -1, 100, 100))); + + EXPECT_INT_RECT_EQ(IntRect(300 + 1, 0, 99, 100), occlusion.unoccludedContentRect(filteredSurface, IntRect(300 + 1, 0, 100, 100))); + EXPECT_INT_RECT_EQ(IntRect(300 + 0, 1, 100, 99), occlusion.unoccludedContentRect(filteredSurface, IntRect(300 + 0, 1, 100, 100))); + EXPECT_INT_RECT_EQ(IntRect(300 + 0, 0, 99, 100), occlusion.unoccludedContentRect(filteredSurface, IntRect(300 - 1, 0, 100, 100))); + EXPECT_INT_RECT_EQ(IntRect(300 + 0, 0, 100, 99), occlusion.unoccludedContentRect(filteredSurface, IntRect(300 + 0, -1, 100, 100))); + this->leaveLayer(filteredSurface, occlusion); + + // The filtered layer/replica does not occlude. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 150), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(5u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 0, 0), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(0u, occlusion.occlusionInTargetSurface().rects().size()); + + // The surface has a background blur, so it needs pixels that are currently considered occluded in order to be drawn. So the pixels + // it needs should be removed some the occluded area so that when we get to the parent they are drawn. + this->visitContributingSurface(filteredSurface, occlusion); + + this->enterLayer(parent, occlusion); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 150), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(5u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 150), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(5u, occlusion.occlusionInTargetSurface().rects().size()); + + IntRect outsetRect; + IntRect testRect; + + // Nothing in the blur outsets for the filteredSurface is occluded. + outsetRect = IntRect(50 - outsetLeft, 50 - outsetTop, 50 + outsetLeft + outsetRight, 50 + outsetTop + outsetBottom); + testRect = outsetRect; + EXPECT_INT_RECT_EQ(outsetRect, occlusion.unoccludedContentRect(parent, testRect)); + + // Stuff outside the blur outsets is still occluded though. + testRect = outsetRect; + testRect.expand(1, 0); + EXPECT_INT_RECT_EQ(outsetRect, occlusion.unoccludedContentRect(parent, testRect)); + testRect = outsetRect; + testRect.expand(0, 1); + EXPECT_INT_RECT_EQ(outsetRect, occlusion.unoccludedContentRect(parent, testRect)); + testRect = outsetRect; + testRect.move(-1, 0); + testRect.expand(1, 0); + EXPECT_INT_RECT_EQ(outsetRect, occlusion.unoccludedContentRect(parent, testRect)); + testRect = outsetRect; + testRect.move(0, -1); + testRect.expand(0, 1); + EXPECT_INT_RECT_EQ(outsetRect, occlusion.unoccludedContentRect(parent, testRect)); + + // Nothing in the blur outsets for the filteredSurface's replica is occluded. + outsetRect = IntRect(200 - outsetLeft, 50 - outsetTop, 50 + outsetLeft + outsetRight, 50 + outsetTop + outsetBottom); + testRect = outsetRect; + EXPECT_INT_RECT_EQ(outsetRect, occlusion.unoccludedContentRect(parent, testRect)); + + // Stuff outside the blur outsets is still occluded though. + testRect = outsetRect; + testRect.expand(1, 0); + EXPECT_INT_RECT_EQ(outsetRect, occlusion.unoccludedContentRect(parent, testRect)); + testRect = outsetRect; + testRect.expand(0, 1); + EXPECT_INT_RECT_EQ(outsetRect, occlusion.unoccludedContentRect(parent, testRect)); + testRect = outsetRect; + testRect.move(-1, 0); + testRect.expand(1, 0); + EXPECT_INT_RECT_EQ(outsetRect, occlusion.unoccludedContentRect(parent, testRect)); + testRect = outsetRect; + testRect.move(0, -1); + testRect.expand(0, 1); + EXPECT_INT_RECT_EQ(outsetRect, occlusion.unoccludedContentRect(parent, testRect)); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestDontOccludePixelsNeededForBackgroundFilter); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestTwoBackgroundFiltersReduceOcclusionTwice : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix scaleByHalf; + scaleByHalf.scale(0.5); + + // Makes two surfaces that completely cover |parent|. The occlusion both above and below the filters will be reduced by each of them. + typename Types::ContentLayerType* root = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(75, 75)); + typename Types::LayerType* parent = this->createSurface(root, scaleByHalf, FloatPoint(0, 0), IntSize(150, 150)); + parent->setMasksToBounds(true); + typename Types::LayerType* filteredSurface1 = this->createDrawingLayer(parent, scaleByHalf, FloatPoint(0, 0), IntSize(300, 300), false); + typename Types::LayerType* filteredSurface2 = this->createDrawingLayer(parent, scaleByHalf, FloatPoint(0, 0), IntSize(300, 300), false); + typename Types::LayerType* occludingLayerAbove = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(100, 100), IntSize(50, 50), true); + + // Filters make the layers own surfaces. + WebFilterOperations filters; + filters.append(WebFilterOperation::createBlurFilter(3)); + filteredSurface1->setBackgroundFilters(filters); + filteredSurface2->setBackgroundFilters(filters); + + // Save the distance of influence for the blur effect. + int outsetTop, outsetRight, outsetBottom, outsetLeft; + filters.getOutsets(outsetTop, outsetRight, outsetBottom, outsetLeft); + + this->calcDrawEtc(root); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(occludingLayerAbove, occlusion); + EXPECT_INT_RECT_EQ(IntRect(100 / 2, 100 / 2, 50 / 2, 50 / 2), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(100, 100, 50, 50), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + this->visitLayer(filteredSurface2, occlusion); + this->visitContributingSurface(filteredSurface2, occlusion); + this->visitLayer(filteredSurface1, occlusion); + this->visitContributingSurface(filteredSurface1, occlusion); + + ASSERT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + ASSERT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + + // Test expectations in the target. + IntRect expectedOcclusion = IntRect(100 + outsetRight * 2, 100 + outsetBottom * 2, 50 - (outsetLeft + outsetRight) * 2, 50 - (outsetTop + outsetBottom) * 2); + EXPECT_INT_RECT_EQ(expectedOcclusion, occlusion.occlusionInTargetSurface().rects()[0]); + + // Test expectations in the screen. Take the ceiling of half of the outsets. + outsetTop = (outsetTop + 1) / 2; + outsetRight = (outsetRight + 1) / 2; + outsetBottom = (outsetBottom + 1) / 2; + outsetLeft = (outsetLeft + 1) / 2; + expectedOcclusion = IntRect(100 / 2 + outsetRight * 2, 100 / 2 + outsetBottom * 2, 50 / 2 - (outsetLeft + outsetRight) * 2, 50 /2 - (outsetTop + outsetBottom) * 2); + + EXPECT_INT_RECT_EQ(expectedOcclusion, occlusion.occlusionInScreenSpace().rects()[0]); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestTwoBackgroundFiltersReduceOcclusionTwice); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestDontOccludePixelsNeededForBackgroundFilterWithClip : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + // Make a surface and its replica, each 50x50, that are completely surrounded by opaque layers which are above them in the z-order. + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 150)); + // We stick the filtered surface inside a clipping surface so that we can make sure the clip is honored when exposing pixels for + // the background filter. + typename Types::LayerType* clippingSurface = this->createSurface(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 70)); + clippingSurface->setMasksToBounds(true); + typename Types::LayerType* filteredSurface = this->createDrawingLayer(clippingSurface, this->identityMatrix, FloatPoint(50, 50), IntSize(50, 50), false); + this->createReplicaLayer(filteredSurface, this->identityMatrix, FloatPoint(150, 0), IntSize()); + typename Types::LayerType* occludingLayer1 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), IntSize(300, 50), true); + typename Types::LayerType* occludingLayer2 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 100), IntSize(300, 50), true); + typename Types::LayerType* occludingLayer3 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 50), IntSize(50, 50), true); + typename Types::LayerType* occludingLayer4 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(100, 50), IntSize(100, 50), true); + typename Types::LayerType* occludingLayer5 = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(250, 50), IntSize(50, 50), true); + + // Filters make the layer own a surface. This filter is large enough that it goes outside the bottom of the clippingSurface. + WebFilterOperations filters; + filters.append(WebFilterOperation::createBlurFilter(12)); + filteredSurface->setBackgroundFilters(filters); + + // Save the distance of influence for the blur effect. + int outsetTop, outsetRight, outsetBottom, outsetLeft; + filters.getOutsets(outsetTop, outsetRight, outsetBottom, outsetLeft); + + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + // These layers occlude pixels directly beside the filteredSurface. Because filtered surface blends pixels in a radius, it will + // need to see some of the pixels (up to radius far) underneath the occludingLayers. + this->visitLayer(occludingLayer5, occlusion); + this->visitLayer(occludingLayer4, occlusion); + this->visitLayer(occludingLayer3, occlusion); + this->visitLayer(occludingLayer2, occlusion); + this->visitLayer(occludingLayer1, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 150), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(5u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 150), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(5u, occlusion.occlusionInTargetSurface().rects().size()); + + // Everything outside the surface/replica is occluded but the surface/replica itself is not. + this->enterLayer(filteredSurface, occlusion); + EXPECT_INT_RECT_EQ(IntRect(1, 0, 49, 50), occlusion.unoccludedContentRect(filteredSurface, IntRect(1, 0, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(0, 1, 50, 49), occlusion.unoccludedContentRect(filteredSurface, IntRect(0, 1, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 49, 50), occlusion.unoccludedContentRect(filteredSurface, IntRect(-1, 0, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 50, 49), occlusion.unoccludedContentRect(filteredSurface, IntRect(0, -1, 50, 50))); + + EXPECT_INT_RECT_EQ(IntRect(150 + 1, 0, 49, 50), occlusion.unoccludedContentRect(filteredSurface, IntRect(150 + 1, 0, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(150 + 0, 1, 50, 49), occlusion.unoccludedContentRect(filteredSurface, IntRect(150 + 0, 1, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(150 + 0, 0, 49, 50), occlusion.unoccludedContentRect(filteredSurface, IntRect(150 - 1, 0, 50, 50))); + EXPECT_INT_RECT_EQ(IntRect(150 + 0, 0, 50, 49), occlusion.unoccludedContentRect(filteredSurface, IntRect(150 + 0, -1, 50, 50))); + this->leaveLayer(filteredSurface, occlusion); + + // The filtered layer/replica does not occlude. + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 150), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(5u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 0, 0), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(0u, occlusion.occlusionInTargetSurface().rects().size()); + + // The surface has a background blur, so it needs pixels that are currently considered occluded in order to be drawn. So the pixels + // it needs should be removed some the occluded area so that when we get to the parent they are drawn. + this->visitContributingSurface(filteredSurface, occlusion); + + this->enterContributingSurface(clippingSurface, occlusion); + EXPECT_INT_RECT_EQ(IntRect(0, 0, 300, 150), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(5u, occlusion.occlusionInScreenSpace().rects().size()); + + IntRect outsetRect; + IntRect clippedOutsetRect; + IntRect testRect; + + // Nothing in the (clipped) blur outsets for the filteredSurface is occluded. + outsetRect = IntRect(50 - outsetLeft, 50 - outsetTop, 50 + outsetLeft + outsetRight, 50 + outsetTop + outsetBottom); + clippedOutsetRect = intersection(outsetRect, IntRect(0 - outsetLeft, 0 - outsetTop, 300 + outsetLeft + outsetRight, 70 + outsetTop + outsetBottom)); + testRect = outsetRect; + EXPECT_INT_RECT_EQ(clippedOutsetRect, occlusion.unoccludedContentRect(clippingSurface, testRect)); + + // Stuff outside the (clipped) blur outsets is still occluded though. + testRect = outsetRect; + testRect.expand(1, 0); + EXPECT_INT_RECT_EQ(clippedOutsetRect, occlusion.unoccludedContentRect(clippingSurface, testRect)); + testRect = outsetRect; + testRect.expand(0, 1); + EXPECT_INT_RECT_EQ(clippedOutsetRect, occlusion.unoccludedContentRect(clippingSurface, testRect)); + testRect = outsetRect; + testRect.move(-1, 0); + testRect.expand(1, 0); + EXPECT_INT_RECT_EQ(clippedOutsetRect, occlusion.unoccludedContentRect(clippingSurface, testRect)); + testRect = outsetRect; + testRect.move(0, -1); + testRect.expand(0, 1); + EXPECT_INT_RECT_EQ(clippedOutsetRect, occlusion.unoccludedContentRect(clippingSurface, testRect)); + + // Nothing in the (clipped) blur outsets for the filteredSurface's replica is occluded. + outsetRect = IntRect(200 - outsetLeft, 50 - outsetTop, 50 + outsetLeft + outsetRight, 50 + outsetTop + outsetBottom); + clippedOutsetRect = intersection(outsetRect, IntRect(0 - outsetLeft, 0 - outsetTop, 300 + outsetLeft + outsetRight, 70 + outsetTop + outsetBottom)); + testRect = outsetRect; + EXPECT_INT_RECT_EQ(clippedOutsetRect, occlusion.unoccludedContentRect(clippingSurface, testRect)); + + // Stuff outside the (clipped) blur outsets is still occluded though. + testRect = outsetRect; + testRect.expand(1, 0); + EXPECT_INT_RECT_EQ(clippedOutsetRect, occlusion.unoccludedContentRect(clippingSurface, testRect)); + testRect = outsetRect; + testRect.expand(0, 1); + EXPECT_INT_RECT_EQ(clippedOutsetRect, occlusion.unoccludedContentRect(clippingSurface, testRect)); + testRect = outsetRect; + testRect.move(-1, 0); + testRect.expand(1, 0); + EXPECT_INT_RECT_EQ(clippedOutsetRect, occlusion.unoccludedContentRect(clippingSurface, testRect)); + testRect = outsetRect; + testRect.move(0, -1); + testRect.expand(0, 1); + EXPECT_INT_RECT_EQ(clippedOutsetRect, occlusion.unoccludedContentRect(clippingSurface, testRect)); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestDontOccludePixelsNeededForBackgroundFilterWithClip); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestDontReduceOcclusionBelowBackgroundFilter : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix scaleByHalf; + scaleByHalf.scale(0.5); + + // Make a surface and its replica, each 50x50, with a smaller 30x30 layer centered below each. + // The surface is scaled to test that the pixel moving is done in the target space, where the background filter is applied, but the surface + // appears at 50, 50 and the replica at 200, 50. + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 150)); + typename Types::LayerType* behindSurfaceLayer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(60, 60), IntSize(30, 30), true); + typename Types::LayerType* behindReplicaLayer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(210, 60), IntSize(30, 30), true); + typename Types::LayerType* filteredSurface = this->createDrawingLayer(parent, scaleByHalf, FloatPoint(50, 50), IntSize(100, 100), false); + this->createReplicaLayer(filteredSurface, this->identityMatrix, FloatPoint(300, 0), IntSize()); + + // Filters make the layer own a surface. + WebFilterOperations filters; + filters.append(WebFilterOperation::createBlurFilter(3)); + filteredSurface->setBackgroundFilters(filters); + + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + // The surface has a background blur, so it blurs non-opaque pixels below it. + this->visitLayer(filteredSurface, occlusion); + this->visitContributingSurface(filteredSurface, occlusion); + + this->visitLayer(behindReplicaLayer, occlusion); + this->visitLayer(behindSurfaceLayer, occlusion); + + // The layers behind the surface are not blurred, and their occlusion does not change, until we leave the surface. + // So it should not be modified by the filter here. + IntRect occlusionBehindSurface = IntRect(60, 60, 30, 30); + IntRect occlusionBehindReplica = IntRect(210, 60, 30, 30); + + IntRect expectedOpaqueBounds = unionRect(occlusionBehindSurface, occlusionBehindReplica); + EXPECT_INT_RECT_EQ(expectedOpaqueBounds, occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(expectedOpaqueBounds, occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInTargetSurface().rects().size()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestDontReduceOcclusionBelowBackgroundFilter); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestDontReduceOcclusionIfBackgroundFilterIsOccluded : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix scaleByHalf; + scaleByHalf.scale(0.5); + + // Make a surface and its replica, each 50x50, that are completely occluded by opaque layers which are above them in the z-order. + // The surface is scaled to test that the pixel moving is done in the target space, where the background filter is applied, but the surface + // appears at 50, 50 and the replica at 200, 50. + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 150)); + typename Types::LayerType* filteredSurface = this->createDrawingLayer(parent, scaleByHalf, FloatPoint(50, 50), IntSize(100, 100), false); + this->createReplicaLayer(filteredSurface, this->identityMatrix, FloatPoint(300, 0), IntSize()); + typename Types::LayerType* aboveSurfaceLayer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(50, 50), IntSize(50, 50), true); + typename Types::LayerType* aboveReplicaLayer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(200, 50), IntSize(50, 50), true); + + // Filters make the layer own a surface. + WebFilterOperations filters; + filters.append(WebFilterOperation::createBlurFilter(3)); + filteredSurface->setBackgroundFilters(filters); + + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(aboveReplicaLayer, occlusion); + this->visitLayer(aboveSurfaceLayer, occlusion); + + // The surface has a background blur, so it blurs non-opaque pixels below it. + this->visitLayer(filteredSurface, occlusion); + this->visitContributingSurface(filteredSurface, occlusion); + + // The filter is completely occluded, so it should not blur anything and reduce any occlusion. + IntRect occlusionAboveSurface = IntRect(50, 50, 50, 50); + IntRect occlusionAboveReplica = IntRect(200, 50, 50, 50); + + IntRect expectedOpaqueBounds = unionRect(occlusionAboveSurface, occlusionAboveReplica); + EXPECT_INT_RECT_EQ(expectedOpaqueBounds, occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(expectedOpaqueBounds, occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(2u, occlusion.occlusionInTargetSurface().rects().size()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestDontReduceOcclusionIfBackgroundFilterIsOccluded); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestReduceOcclusionWhenBackgroundFilterIsPartiallyOccluded : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + WebTransformationMatrix scaleByHalf; + scaleByHalf.scale(0.5); + + // Make a surface and its replica, each 50x50, that are partially occluded by opaque layers which are above them in the z-order. + // The surface is scaled to test that the pixel moving is done in the target space, where the background filter is applied, but the surface + // appears at 50, 50 and the replica at 200, 50. + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(300, 150)); + typename Types::LayerType* filteredSurface = this->createDrawingLayer(parent, scaleByHalf, FloatPoint(50, 50), IntSize(100, 100), false); + this->createReplicaLayer(filteredSurface, this->identityMatrix, FloatPoint(300, 0), IntSize()); + typename Types::LayerType* aboveSurfaceLayer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(70, 50), IntSize(30, 50), true); + typename Types::LayerType* aboveReplicaLayer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(200, 50), IntSize(30, 50), true); + typename Types::LayerType* besideSurfaceLayer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(90, 40), IntSize(10, 10), true); + typename Types::LayerType* besideReplicaLayer = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(200, 40), IntSize(10, 10), true); + + // Filters make the layer own a surface. + WebFilterOperations filters; + filters.append(WebFilterOperation::createBlurFilter(3)); + filteredSurface->setBackgroundFilters(filters); + + // Save the distance of influence for the blur effect. + int outsetTop, outsetRight, outsetBottom, outsetLeft; + filters.getOutsets(outsetTop, outsetRight, outsetBottom, outsetLeft); + + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + + this->visitLayer(besideReplicaLayer, occlusion); + this->visitLayer(besideSurfaceLayer, occlusion); + this->visitLayer(aboveReplicaLayer, occlusion); + this->visitLayer(aboveSurfaceLayer, occlusion); + + // The surface has a background blur, so it blurs non-opaque pixels below it. + this->visitLayer(filteredSurface, occlusion); + this->visitContributingSurface(filteredSurface, occlusion); + + // The filter in the surface and replica are partially unoccluded. Only the unoccluded parts should reduce occlusion. + // This means it will push back the occlusion that touches the unoccluded part (occlusionAbove___), but it will not + // touch occlusionBeside____ since that is not beside the unoccluded part of the surface, even though it is beside + // the occluded part of the surface. + IntRect occlusionAboveSurface = IntRect(70 + outsetRight, 50, 30 - outsetRight, 50); + IntRect occlusionAboveReplica = IntRect(200, 50, 30 - outsetLeft, 50); + IntRect occlusionBesideSurface = IntRect(90, 40, 10, 10); + IntRect occlusionBesideReplica = IntRect(200, 40, 10, 10); + + Region expectedOcclusion; + expectedOcclusion.unite(occlusionAboveSurface); + expectedOcclusion.unite(occlusionAboveReplica); + expectedOcclusion.unite(occlusionBesideSurface); + expectedOcclusion.unite(occlusionBesideReplica); + + ASSERT_EQ(expectedOcclusion.rects().size(), occlusion.occlusionInTargetSurface().rects().size()); + ASSERT_EQ(expectedOcclusion.rects().size(), occlusion.occlusionInScreenSpace().rects().size()); + + for (size_t i = 0; i < expectedOcclusion.rects().size(); ++i) { + IntRect expectedRect = expectedOcclusion.rects()[i]; + IntRect screenRect = occlusion.occlusionInScreenSpace().rects()[i]; + IntRect targetRect = occlusion.occlusionInTargetSurface().rects()[i]; + EXPECT_EQ(expectedRect, screenRect); + EXPECT_EQ(expectedRect, targetRect); + } + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestReduceOcclusionWhenBackgroundFilterIsPartiallyOccluded); + +template<class Types, bool opaqueLayers> +class CCOcclusionTrackerTestMinimumTrackingSize : public CCOcclusionTrackerTest<Types, opaqueLayers> { +protected: + void runMyTest() + { + IntSize trackingSize(100, 100); + IntSize belowTrackingSize(99, 99); + + typename Types::ContentLayerType* parent = this->createRoot(this->identityMatrix, FloatPoint(0, 0), IntSize(400, 400)); + typename Types::LayerType* large = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), trackingSize, true); + typename Types::LayerType* small = this->createDrawingLayer(parent, this->identityMatrix, FloatPoint(0, 0), belowTrackingSize, true); + this->calcDrawEtc(parent); + + TestCCOcclusionTrackerWithClip<typename Types::LayerType, typename Types::RenderSurfaceType> occlusion(IntRect(0, 0, 1000, 1000)); + occlusion.setLayerClipRect(IntRect(0, 0, 1000, 1000)); + occlusion.setMinimumTrackingSize(trackingSize); + + // The small layer is not tracked because it is too small. + this->visitLayer(small, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(0u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(0u, occlusion.occlusionInTargetSurface().rects().size()); + + // The large layer is tracked as it is large enough. + this->visitLayer(large, occlusion); + + EXPECT_INT_RECT_EQ(IntRect(IntPoint(), trackingSize), occlusion.occlusionInScreenSpace().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size()); + EXPECT_INT_RECT_EQ(IntRect(IntPoint(), trackingSize), occlusion.occlusionInTargetSurface().bounds()); + EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size()); + } +}; + +ALL_CCOCCLUSIONTRACKER_TEST(CCOcclusionTrackerTestMinimumTrackingSize); + +} // namespace diff --git a/cc/CCOverdrawMetrics.cpp b/cc/CCOverdrawMetrics.cpp new file mode 100644 index 0000000..51fbae6 --- /dev/null +++ b/cc/CCOverdrawMetrics.cpp @@ -0,0 +1,190 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCOverdrawMetrics.h" + +#include "CCLayerTreeHost.h" +#include "CCLayerTreeHostImpl.h" +#include "CCMathUtil.h" +#include "FloatQuad.h" +#include "IntRect.h" +#include "TraceEvent.h" +#include <public/Platform.h> +#include <public/WebTransformationMatrix.h> + +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +CCOverdrawMetrics::CCOverdrawMetrics(bool recordMetricsForFrame) + : m_recordMetricsForFrame(recordMetricsForFrame) + , m_pixelsPainted(0) + , m_pixelsUploadedOpaque(0) + , m_pixelsUploadedTranslucent(0) + , m_tilesCulledForUpload(0) + , m_contentsTextureUseBytes(0) + , m_renderSurfaceTextureUseBytes(0) + , m_pixelsDrawnOpaque(0) + , m_pixelsDrawnTranslucent(0) + , m_pixelsCulledForDrawing(0) +{ +} + +static inline float wedgeProduct(const FloatPoint& p1, const FloatPoint& p2) +{ + return p1.x() * p2.y() - p1.y() * p2.x(); +} + +// Calculates area of an arbitrary convex polygon with up to 8 points. +static inline float polygonArea(const FloatPoint points[8], int numPoints) +{ + if (numPoints < 3) + return 0; + + float area = 0; + for (int i = 0; i < numPoints; ++i) + area += wedgeProduct(points[i], points[(i+1)%numPoints]); + return fabs(0.5f * area); +} + +// Takes a given quad, maps it by the given transformation, and gives the area of the resulting polygon. +static inline float areaOfMappedQuad(const WebTransformationMatrix& transform, const FloatQuad& quad) +{ + FloatPoint clippedQuad[8]; + int numVerticesInClippedQuad = 0; + CCMathUtil::mapClippedQuad(transform, quad, clippedQuad, numVerticesInClippedQuad); + return polygonArea(clippedQuad, numVerticesInClippedQuad); +} + +void CCOverdrawMetrics::didPaint(const IntRect& paintedRect) +{ + if (!m_recordMetricsForFrame) + return; + + m_pixelsPainted += static_cast<float>(paintedRect.width()) * paintedRect.height(); +} + +void CCOverdrawMetrics::didCullTileForUpload() +{ + if (m_recordMetricsForFrame) + ++m_tilesCulledForUpload; +} + +void CCOverdrawMetrics::didUpload(const WebTransformationMatrix& transformToTarget, const IntRect& uploadRect, const IntRect& opaqueRect) +{ + if (!m_recordMetricsForFrame) + return; + + float uploadArea = areaOfMappedQuad(transformToTarget, FloatQuad(uploadRect)); + float uploadOpaqueArea = areaOfMappedQuad(transformToTarget, FloatQuad(intersection(opaqueRect, uploadRect))); + + m_pixelsUploadedOpaque += uploadOpaqueArea; + m_pixelsUploadedTranslucent += uploadArea - uploadOpaqueArea; +} + +void CCOverdrawMetrics::didUseContentsTextureMemoryBytes(size_t contentsTextureUseBytes) +{ + if (!m_recordMetricsForFrame) + return; + + m_contentsTextureUseBytes += contentsTextureUseBytes; +} + +void CCOverdrawMetrics::didUseRenderSurfaceTextureMemoryBytes(size_t renderSurfaceUseBytes) +{ + if (!m_recordMetricsForFrame) + return; + + m_renderSurfaceTextureUseBytes += renderSurfaceUseBytes; +} + +void CCOverdrawMetrics::didCullForDrawing(const WebTransformationMatrix& transformToTarget, const IntRect& beforeCullRect, const IntRect& afterCullRect) +{ + if (!m_recordMetricsForFrame) + return; + + float beforeCullArea = areaOfMappedQuad(transformToTarget, FloatQuad(beforeCullRect)); + float afterCullArea = areaOfMappedQuad(transformToTarget, FloatQuad(afterCullRect)); + + m_pixelsCulledForDrawing += beforeCullArea - afterCullArea; +} + +void CCOverdrawMetrics::didDraw(const WebTransformationMatrix& transformToTarget, const IntRect& afterCullRect, const IntRect& opaqueRect) +{ + if (!m_recordMetricsForFrame) + return; + + float afterCullArea = areaOfMappedQuad(transformToTarget, FloatQuad(afterCullRect)); + float afterCullOpaqueArea = areaOfMappedQuad(transformToTarget, FloatQuad(intersection(opaqueRect, afterCullRect))); + + m_pixelsDrawnOpaque += afterCullOpaqueArea; + m_pixelsDrawnTranslucent += afterCullArea - afterCullOpaqueArea; +} + +void CCOverdrawMetrics::recordMetrics(const CCLayerTreeHost* layerTreeHost) const +{ + if (m_recordMetricsForFrame) + recordMetricsInternal<CCLayerTreeHost>(UpdateAndCommit, layerTreeHost); +} + +void CCOverdrawMetrics::recordMetrics(const CCLayerTreeHostImpl* layerTreeHost) const +{ + if (m_recordMetricsForFrame) + recordMetricsInternal<CCLayerTreeHostImpl>(DrawingToScreen, layerTreeHost); +} + +template<typename LayerTreeHostType> +void CCOverdrawMetrics::recordMetricsInternal(MetricsType metricsType, const LayerTreeHostType* layerTreeHost) const +{ + // This gives approximately 10x the percentage of pixels to fill the viewport once. + float normalization = 1000.f / (layerTreeHost->deviceViewportSize().width() * layerTreeHost->deviceViewportSize().height()); + // This gives approximately 100x the percentage of tiles to fill the viewport once, if all tiles were 256x256. + float tileNormalization = 10000.f / (layerTreeHost->deviceViewportSize().width() / 256.f * layerTreeHost->deviceViewportSize().height() / 256.f); + // This gives approximately 10x the percentage of bytes to fill the viewport once, assuming 4 bytes per pixel. + float byteNormalization = normalization / 4; + + switch (metricsType) { + case DrawingToScreen: + WebKit::Platform::current()->histogramCustomCounts("Renderer4.pixelCountOpaque_Draw", static_cast<int>(normalization * m_pixelsDrawnOpaque), 100, 1000000, 50); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.pixelCountTranslucent_Draw", static_cast<int>(normalization * m_pixelsDrawnTranslucent), 100, 1000000, 50); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.pixelCountCulled_Draw", static_cast<int>(normalization * m_pixelsCulledForDrawing), 100, 1000000, 50); + + { + TRACE_COUNTER_ID1("cc", "DrawPixelsCulled", layerTreeHost, m_pixelsCulledForDrawing); + TRACE_EVENT2("cc", "CCOverdrawMetrics", "PixelsDrawnOpaque", m_pixelsDrawnOpaque, "PixelsDrawnTranslucent", m_pixelsDrawnTranslucent); + } + break; + case UpdateAndCommit: + WebKit::Platform::current()->histogramCustomCounts("Renderer4.pixelCountPainted", static_cast<int>(normalization * m_pixelsPainted), 100, 1000000, 50); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.pixelCountOpaque_Upload", static_cast<int>(normalization * m_pixelsUploadedOpaque), 100, 1000000, 50); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.pixelCountTranslucent_Upload", static_cast<int>(normalization * m_pixelsUploadedTranslucent), 100, 1000000, 50); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.tileCountCulled_Upload", static_cast<int>(tileNormalization * m_tilesCulledForUpload), 100, 10000000, 50); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.renderSurfaceTextureBytes_ViewportScaled", static_cast<int>(byteNormalization * m_renderSurfaceTextureUseBytes), 10, 1000000, 50); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.renderSurfaceTextureBytes_Unscaled", static_cast<int>(m_renderSurfaceTextureUseBytes / 1000), 1000, 100000000, 50); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.contentsTextureBytes_ViewportScaled", static_cast<int>(byteNormalization * m_contentsTextureUseBytes), 10, 1000000, 50); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.contentsTextureBytes_Unscaled", static_cast<int>(m_contentsTextureUseBytes / 1000), 1000, 100000000, 50); + + { + TRACE_COUNTER_ID1("cc", "UploadTilesCulled", layerTreeHost, m_tilesCulledForUpload); + TRACE_EVENT2("cc", "CCOverdrawMetrics", "PixelsUploadedOpaque", m_pixelsUploadedOpaque, "PixelsUploadedTranslucent", m_pixelsUploadedTranslucent); + } + { + // This must be in a different scope than the TRACE_EVENT2 above. + TRACE_EVENT1("cc", "CCOverdrawPaintMetrics", "PixelsPainted", m_pixelsPainted); + } + { + // This must be in a different scope than the TRACE_EVENTs above. + TRACE_EVENT2("cc", "CCOverdrawPaintMetrics", "ContentsTextureBytes", m_contentsTextureUseBytes, "RenderSurfaceTextureBytes", m_renderSurfaceTextureUseBytes); + } + break; + } +} + +} // namespace WebCore + +#endif diff --git a/cc/CCOverdrawMetrics.h b/cc/CCOverdrawMetrics.h new file mode 100644 index 0000000..3c1a386 --- /dev/null +++ b/cc/CCOverdrawMetrics.h @@ -0,0 +1,97 @@ +// Copyright 2012 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. + +#ifndef CCOverdrawMetrics_h +#define CCOverdrawMetrics_h + +#include <wtf/PassOwnPtr.h> + +namespace WebKit { +class WebTransformationMatrix; +} + +namespace WebCore { +class IntRect; +class CCLayerTreeHost; +class CCLayerTreeHostImpl; + +// FIXME: compute overdraw metrics only occasionally, not every frame. +class CCOverdrawMetrics { +public: + static PassOwnPtr<CCOverdrawMetrics> create(bool recordMetricsForFrame) { return adoptPtr(new CCOverdrawMetrics(recordMetricsForFrame)); } + + // These methods are used for saving metrics during update/commit. + + // Record pixels painted by WebKit into the texture updater, but does not mean the pixels were rasterized in main memory. + void didPaint(const IntRect& paintedRect); + // Records that an invalid tile was culled and did not need to be painted/uploaded, and did not contribute to other tiles needing to be painted. + void didCullTileForUpload(); + // Records pixels that were uploaded to texture memory. + void didUpload(const WebKit::WebTransformationMatrix& transformToTarget, const IntRect& uploadRect, const IntRect& opaqueRect); + // Record contents texture(s) behind present using the given number of bytes. + void didUseContentsTextureMemoryBytes(size_t contentsTextureUseBytes); + // Record RenderSurface texture(s) being present using the given number of bytes. + void didUseRenderSurfaceTextureMemoryBytes(size_t renderSurfaceUseBytes); + + // These methods are used for saving metrics during draw. + + // Record pixels that were not drawn to screen. + void didCullForDrawing(const WebKit::WebTransformationMatrix& transformToTarget, const IntRect& beforeCullRect, const IntRect& afterCullRect); + // Record pixels that were drawn to screen. + void didDraw(const WebKit::WebTransformationMatrix& transformToTarget, const IntRect& afterCullRect, const IntRect& opaqueRect); + + void recordMetrics(const CCLayerTreeHost*) const; + void recordMetrics(const CCLayerTreeHostImpl*) const; + + // Accessors for tests. + float pixelsDrawnOpaque() const { return m_pixelsDrawnOpaque; } + float pixelsDrawnTranslucent() const { return m_pixelsDrawnTranslucent; } + float pixelsCulledForDrawing() const { return m_pixelsCulledForDrawing; } + float pixelsPainted() const { return m_pixelsPainted; } + float pixelsUploadedOpaque() const { return m_pixelsUploadedOpaque; } + float pixelsUploadedTranslucent() const { return m_pixelsUploadedTranslucent; } + int tilesCulledForUpload() const { return m_tilesCulledForUpload; } + +private: + enum MetricsType { + UpdateAndCommit, + DrawingToScreen + }; + + explicit CCOverdrawMetrics(bool recordMetricsForFrame); + + template<typename LayerTreeHostType> + void recordMetricsInternal(MetricsType, const LayerTreeHostType*) const; + + // When false this class is a giant no-op. + bool m_recordMetricsForFrame; + + // These values are used for saving metrics during update/commit. + + // Count of pixels that were painted due to invalidation. + float m_pixelsPainted; + // Count of pixels uploaded to textures and known to be opaque. + float m_pixelsUploadedOpaque; + // Count of pixels uploaded to textures and not known to be opaque. + float m_pixelsUploadedTranslucent; + // Count of tiles that were invalidated but not uploaded. + int m_tilesCulledForUpload; + // Count the number of bytes in contents textures. + unsigned long long m_contentsTextureUseBytes; + // Count the number of bytes in RenderSurface textures. + unsigned long long m_renderSurfaceTextureUseBytes; + + // These values are used for saving metrics during draw. + + // Count of pixels that are opaque (and thus occlude). Ideally this is no more than wiewport width x height. + float m_pixelsDrawnOpaque; + // Count of pixels that are possibly translucent, and cannot occlude. + float m_pixelsDrawnTranslucent; + // Count of pixels not drawn as they are occluded by somthing opaque. + float m_pixelsCulledForDrawing; +}; + +} // namespace WebCore + +#endif diff --git a/cc/CCPageScaleAnimation.cpp b/cc/CCPageScaleAnimation.cpp new file mode 100644 index 0000000..ab031924 --- /dev/null +++ b/cc/CCPageScaleAnimation.cpp @@ -0,0 +1,159 @@ +// Copyright 2012 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 "CCPageScaleAnimation.h" + +#include "FloatRect.h" +#include "FloatSize.h" + +#include <math.h> + +namespace WebCore { + +PassOwnPtr<CCPageScaleAnimation> CCPageScaleAnimation::create(const IntSize& scrollStart, float pageScaleStart, const IntSize& windowSize, const IntSize& contentSize, double startTime) +{ + return adoptPtr(new CCPageScaleAnimation(scrollStart, pageScaleStart, windowSize, contentSize, startTime)); +} + + +CCPageScaleAnimation::CCPageScaleAnimation(const IntSize& scrollStart, float pageScaleStart, const IntSize& windowSize, const IntSize& contentSize, double startTime) + : m_scrollStart(scrollStart) + , m_pageScaleStart(pageScaleStart) + , m_windowSize(windowSize) + , m_contentSize(contentSize) + , m_anchorMode(false) + , m_scrollEnd(scrollStart) + , m_pageScaleEnd(pageScaleStart) + , m_startTime(startTime) + , m_duration(0) +{ +} + +void CCPageScaleAnimation::zoomTo(const IntSize& finalScroll, float finalPageScale, double duration) +{ + if (m_pageScaleStart != finalPageScale) { + // For uniform-looking zooming, infer the anchor (point that remains in + // place throughout the zoom) from the start and end rects. + FloatRect startRect(IntPoint(m_scrollStart), m_windowSize); + FloatRect endRect(IntPoint(finalScroll), m_windowSize); + endRect.scale(m_pageScaleStart / finalPageScale); + + // The anchor is the point which is at the same ratio of the sides of + // both startRect and endRect. For example, a zoom-in double-tap to a + // perfectly centered rect will have anchor ratios (0.5, 0.5), while one + // to a rect touching the bottom-right of the screen will have anchor + // ratios (1.0, 1.0). In other words, it obeys the equations: + // anchorX = start_width * ratioX + start_x + // anchorX = end_width * ratioX + end_x + // anchorY = start_height * ratioY + start_y + // anchorY = end_height * ratioY + end_y + // where both anchor{x,y} and ratio{x,y} begin as unknowns. Solving + // for the ratios, we get the following formulas: + float ratioX = (startRect.x() - endRect.x()) / (endRect.width() - startRect.width()); + float ratioY = (startRect.y() - endRect.y()) / (endRect.height() - startRect.height()); + + IntSize anchor(m_windowSize.width() * ratioX, m_windowSize.height() * ratioY); + zoomWithAnchor(anchor, finalPageScale, duration); + } else { + // If this is a pure translation, then there exists no anchor. Linearly + // interpolate the scroll offset instead. + m_scrollEnd = finalScroll; + m_pageScaleEnd = finalPageScale; + m_duration = duration; + m_anchorMode = false; + } +} + +void CCPageScaleAnimation::zoomWithAnchor(const IntSize& anchor, float finalPageScale, double duration) +{ + m_scrollEnd = m_scrollStart + anchor; + m_scrollEnd.scale(finalPageScale / m_pageScaleStart); + m_scrollEnd -= anchor; + + m_scrollEnd.clampNegativeToZero(); + FloatSize scaledContentSize(m_contentSize); + scaledContentSize.scale(finalPageScale / m_pageScaleStart); + IntSize maxScrollPosition = roundedIntSize(scaledContentSize - m_windowSize); + m_scrollEnd = m_scrollEnd.shrunkTo(maxScrollPosition); + + m_anchor = anchor; + m_pageScaleEnd = finalPageScale; + m_duration = duration; + m_anchorMode = true; +} + +IntSize CCPageScaleAnimation::scrollOffsetAtTime(double time) const +{ + return scrollOffsetAtRatio(progressRatioForTime(time)); +} + +float CCPageScaleAnimation::pageScaleAtTime(double time) const +{ + return pageScaleAtRatio(progressRatioForTime(time)); +} + +bool CCPageScaleAnimation::isAnimationCompleteAtTime(double time) const +{ + return time >= endTime(); +} + +float CCPageScaleAnimation::progressRatioForTime(double time) const +{ + if (isAnimationCompleteAtTime(time)) + return 1; + + return (time - m_startTime) / m_duration; +} + +IntSize CCPageScaleAnimation::scrollOffsetAtRatio(float ratio) const +{ + if (ratio <= 0) + return m_scrollStart; + if (ratio >= 1) + return m_scrollEnd; + + float currentPageScale = pageScaleAtRatio(ratio); + IntSize currentScrollOffset; + if (m_anchorMode) { + // Keep the anchor stable on the screen at the current scale. + IntSize documentAnchor = m_scrollStart + m_anchor; + documentAnchor.scale(currentPageScale / m_pageScaleStart); + currentScrollOffset = documentAnchor - m_anchor; + } else { + // First move both scroll offsets to the current coordinate space. + FloatSize scaledStartScroll(m_scrollStart); + scaledStartScroll.scale(currentPageScale / m_pageScaleStart); + FloatSize scaledEndScroll(m_scrollEnd); + scaledEndScroll.scale(currentPageScale / m_pageScaleEnd); + + // Linearly interpolate between them. + FloatSize delta = scaledEndScroll - scaledStartScroll; + delta.scale(ratio); + currentScrollOffset = roundedIntSize(scaledStartScroll + delta); + } + + return currentScrollOffset; +} + +float CCPageScaleAnimation::pageScaleAtRatio(float ratio) const +{ + if (ratio <= 0) + return m_pageScaleStart; + if (ratio >= 1) + return m_pageScaleEnd; + + // Linearly interpolate the magnitude in log scale. + // Log scale is needed to maintain the appearance of uniform zoom. For + // example, if we zoom from 0.5 to 4.0 in 3 seconds, then we should + // be zooming by 2x every second. + float diff = m_pageScaleEnd / m_pageScaleStart; + float logDiff = log(diff); + logDiff *= ratio; + diff = exp(logDiff); + return m_pageScaleStart * diff; +} + +} // namespace WebCore diff --git a/cc/CCPageScaleAnimation.h b/cc/CCPageScaleAnimation.h new file mode 100644 index 0000000..3e1c900 --- /dev/null +++ b/cc/CCPageScaleAnimation.h @@ -0,0 +1,74 @@ +// 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. + +#ifndef CCPageScaleAnimation_h +#define CCPageScaleAnimation_h + +#include "IntSize.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +// A small helper class that does the math for zoom animations, primarily for +// double-tap zoom. Initialize it with starting and ending scroll/page scale +// positions and an animation length time, then call ...AtTime() at every frame +// to obtain the current interpolated position. +class CCPageScaleAnimation { +public: + // Construct with the starting page scale and scroll offset (which is in + // pageScaleStart space). The window size is the user-viewable area + // in pixels. + static PassOwnPtr<CCPageScaleAnimation> create(const IntSize& scrollStart, float pageScaleStart, const IntSize& windowSize, const IntSize& contentSize, double startTime); + + // The following methods initialize the animation. Call one of them + // immediately after construction to set the final scroll and page scale. + + // Zoom while explicitly specifying the top-left scroll position. The + // scroll offset is in finalPageScale coordinates. + void zoomTo(const IntSize& finalScroll, float finalPageScale, double duration); + + // Zoom based on a specified onscreen anchor, which will remain at the same + // position on the screen throughout the animation. The anchor is in local + // space relative to scrollStart. + void zoomWithAnchor(const IntSize& anchor, float finalPageScale, double duration); + + // Call these functions while the animation is in progress to output the + // current state. + IntSize scrollOffsetAtTime(double time) const; + float pageScaleAtTime(double time) const; + bool isAnimationCompleteAtTime(double time) const; + + // The following methods return state which is invariant throughout the + // course of the animation. + double startTime() const { return m_startTime; } + double duration() const { return m_duration; } + double endTime() const { return m_startTime + m_duration; } + const IntSize& finalScrollOffset() const { return m_scrollEnd; } + float finalPageScale() const { return m_pageScaleEnd; } + +protected: + CCPageScaleAnimation(const IntSize& scrollStart, float pageScaleStart, const IntSize& windowSize, const IntSize& contentSize, double startTime); + +private: + float progressRatioForTime(double time) const; + IntSize scrollOffsetAtRatio(float ratio) const; + float pageScaleAtRatio(float ratio) const; + + IntSize m_scrollStart; + float m_pageScaleStart; + IntSize m_windowSize; + IntSize m_contentSize; + + bool m_anchorMode; + IntSize m_anchor; + IntSize m_scrollEnd; + float m_pageScaleEnd; + + double m_startTime; + double m_duration; +}; + +} // namespace WebCore + +#endif diff --git a/cc/CCPrioritizedTexture.cpp b/cc/CCPrioritizedTexture.cpp new file mode 100644 index 0000000..8a1e7a1 --- /dev/null +++ b/cc/CCPrioritizedTexture.cpp @@ -0,0 +1,122 @@ +// Copyright 2012 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 "CCPrioritizedTexture.h" + +#include "CCPrioritizedTextureManager.h" +#include "CCPriorityCalculator.h" +#include <algorithm> + +using namespace std; + +namespace WebCore { + +CCPrioritizedTexture::CCPrioritizedTexture(CCPrioritizedTextureManager* manager, IntSize size, GC3Denum format) + : m_size(size) + , m_format(format) + , m_bytes(0) + , m_priority(CCPriorityCalculator::lowestPriority()) + , m_isAbovePriorityCutoff(false) + , m_isSelfManaged(false) + , m_backing(0) + , m_manager(0) +{ + // m_manager is set in registerTexture() so validity can be checked. + ASSERT(format || size.isEmpty()); + if (format) + m_bytes = CCTexture::memorySizeBytes(size, format); + if (manager) + manager->registerTexture(this); +} + +CCPrioritizedTexture::~CCPrioritizedTexture() +{ + if (m_manager) + m_manager->unregisterTexture(this); +} + +void CCPrioritizedTexture::setTextureManager(CCPrioritizedTextureManager* manager) +{ + if (m_manager == manager) + return; + if (m_manager) + m_manager->unregisterTexture(this); + if (manager) + manager->registerTexture(this); +} + +void CCPrioritizedTexture::setDimensions(IntSize size, GC3Denum format) +{ + if (m_format != format || m_size != size) { + m_isAbovePriorityCutoff = false; + m_format = format; + m_size = size; + m_bytes = CCTexture::memorySizeBytes(size, format); + ASSERT(m_manager || !m_backing); + if (m_manager) + m_manager->returnBackingTexture(this); + } +} + +bool CCPrioritizedTexture::requestLate() +{ + if (!m_manager) + return false; + return m_manager->requestLate(this); +} + +void CCPrioritizedTexture::acquireBackingTexture(CCResourceProvider* resourceProvider) +{ + ASSERT(m_isAbovePriorityCutoff); + if (m_isAbovePriorityCutoff) + m_manager->acquireBackingTextureIfNeeded(this, resourceProvider); +} + +CCResourceProvider::ResourceId CCPrioritizedTexture::resourceId() const +{ + if (m_backing) + return m_backing->id(); + return 0; +} + +void CCPrioritizedTexture::upload(CCResourceProvider* resourceProvider, + const uint8_t* image, const IntRect& imageRect, + const IntRect& sourceRect, const IntSize& destOffset) +{ + ASSERT(m_isAbovePriorityCutoff); + if (m_isAbovePriorityCutoff) + acquireBackingTexture(resourceProvider); + ASSERT(m_backing); + resourceProvider->upload(resourceId(), image, imageRect, sourceRect, destOffset); +} + +void CCPrioritizedTexture::link(Backing* backing) +{ + ASSERT(backing); + ASSERT(!backing->m_owner); + ASSERT(!m_backing); + + m_backing = backing; + m_backing->m_owner = this; +} + +void CCPrioritizedTexture::unlink() +{ + ASSERT(m_backing); + ASSERT(m_backing->m_owner == this); + + m_backing->m_owner = 0; + m_backing = 0; +} + +void CCPrioritizedTexture::setToSelfManagedMemoryPlaceholder(size_t bytes) +{ + setDimensions(IntSize(), GraphicsContext3D::RGBA); + setIsSelfManaged(true); + m_bytes = bytes; +} + +} // namespace WebCore diff --git a/cc/CCPrioritizedTexture.h b/cc/CCPrioritizedTexture.h new file mode 100644 index 0000000..ec90a49 --- /dev/null +++ b/cc/CCPrioritizedTexture.h @@ -0,0 +1,121 @@ +// Copyright 2012 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. + +#ifndef CCPrioritizedTexture_h +#define CCPrioritizedTexture_h + +#include "CCPriorityCalculator.h" +#include "CCResourceProvider.h" +#include "CCTexture.h" +#include "GraphicsContext3D.h" +#include "IntRect.h" +#include "IntSize.h" + +namespace WebCore { + +class CCPrioritizedTextureManager; + +class CCPrioritizedTexture { + WTF_MAKE_NONCOPYABLE(CCPrioritizedTexture); +public: + static PassOwnPtr<CCPrioritizedTexture> create(CCPrioritizedTextureManager* manager, IntSize size, GC3Denum format) + { + return adoptPtr(new CCPrioritizedTexture(manager, size, format)); + } + static PassOwnPtr<CCPrioritizedTexture> create(CCPrioritizedTextureManager* manager) + { + return adoptPtr(new CCPrioritizedTexture(manager, IntSize(), 0)); + } + ~CCPrioritizedTexture(); + + // Texture properties. Changing these causes the backing texture to be lost. + // Setting these to the same value is a no-op. + void setTextureManager(CCPrioritizedTextureManager*); + CCPrioritizedTextureManager* textureManager() { return m_manager; } + void setDimensions(IntSize, GC3Denum format); + GC3Denum format() const { return m_format; } + IntSize size() const { return m_size; } + size_t bytes() const { return m_bytes; } + + // Set priority for the requested texture. + void setRequestPriority(int priority) { m_priority = priority; } + int requestPriority() const { return m_priority; } + + // After CCPrioritizedTexture::prioritizeTextures() is called, this returns + // if the the request succeeded and this texture can be acquired for use. + bool canAcquireBackingTexture() const { return m_isAbovePriorityCutoff; } + + // This returns whether we still have a backing texture. This can continue + // to be true even after canAcquireBackingTexture() becomes false. In this + // case the texture can be used but shouldn't be updated since it will get + // taken away "soon". + bool haveBackingTexture() const { return !!backing(); } + + // If canAcquireBackingTexture() is true acquireBackingTexture() will acquire + // a backing texture for use. Call this whenever the texture is actually needed. + void acquireBackingTexture(CCResourceProvider*); + + // FIXME: Request late is really a hack for when we are totally out of memory + // (all textures are visible) but we can still squeeze into the limit + // by not painting occluded textures. In this case the manager + // refuses all visible textures and requestLate() will enable + // canAcquireBackingTexture() on a call-order basis. We might want to + // just remove this in the future (carefully) and just make sure we don't + // regress OOMs situations. + bool requestLate(); + + // Uploads pixels into the backing resource. This functions will aquire the backing if needed. + void upload(CCResourceProvider*, const uint8_t* image, const IntRect& imageRect, const IntRect& sourceRect, const IntSize& destOffset); + + CCResourceProvider::ResourceId resourceId() const; + + // Self-managed textures are accounted for when prioritizing other textures, + // but they are not allocated/recycled/deleted, so this needs to be done + // externally. canAcquireBackingTexture() indicates if the texture would have + // been allowed given its priority. + void setIsSelfManaged(bool isSelfManaged) { m_isSelfManaged = isSelfManaged; } + bool isSelfManaged() { return m_isSelfManaged; } + void setToSelfManagedMemoryPlaceholder(size_t bytes); + +private: + friend class CCPrioritizedTextureManager; + + class Backing : public CCTexture { + WTF_MAKE_NONCOPYABLE(Backing); + public: + Backing(unsigned id, IntSize size, GC3Denum format) + : CCTexture(id, size, format), m_owner(0) { } + ~Backing() { ASSERT(!m_owner); } + + CCPrioritizedTexture* owner() { return m_owner; } + private: + friend class CCPrioritizedTexture; + CCPrioritizedTexture* m_owner; + }; + + CCPrioritizedTexture(CCPrioritizedTextureManager*, IntSize, GC3Denum format); + + bool isAbovePriorityCutoff() { return m_isAbovePriorityCutoff; } + void setAbovePriorityCutoff(bool isAbovePriorityCutoff) { m_isAbovePriorityCutoff = isAbovePriorityCutoff; } + void setManagerInternal(CCPrioritizedTextureManager* manager) { m_manager = manager; } + + Backing* backing() const { return m_backing; } + void link(Backing*); + void unlink(); + + IntSize m_size; + GC3Denum m_format; + size_t m_bytes; + + size_t m_priority; + bool m_isAbovePriorityCutoff; + bool m_isSelfManaged; + + Backing* m_backing; + CCPrioritizedTextureManager* m_manager; +}; + +} // WebCore + +#endif diff --git a/cc/CCPrioritizedTextureManager.cpp b/cc/CCPrioritizedTextureManager.cpp new file mode 100644 index 0000000..2678ec9 --- /dev/null +++ b/cc/CCPrioritizedTextureManager.cpp @@ -0,0 +1,339 @@ +// Copyright 2012 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 "CCPrioritizedTextureManager.h" + +#include "CCPrioritizedTexture.h" +#include "CCPriorityCalculator.h" +#include "TraceEvent.h" +#include <algorithm> + +using namespace std; + +namespace WebCore { + +CCPrioritizedTextureManager::CCPrioritizedTextureManager(size_t maxMemoryLimitBytes, int, int pool) + : m_maxMemoryLimitBytes(maxMemoryLimitBytes) + , m_memoryUseBytes(0) + , m_memoryAboveCutoffBytes(0) + , m_memoryAvailableBytes(0) + , m_pool(pool) +{ +} + +CCPrioritizedTextureManager::~CCPrioritizedTextureManager() +{ + while (m_textures.size() > 0) + unregisterTexture(*m_textures.begin()); + + // Each remaining backing is a leaked opengl texture. We don't have the resourceProvider + // to delete the textures at this time so clearMemory() needs to be called before this. + while (m_backings.size() > 0) + destroyBacking(*m_backings.begin(), 0); +} + +void CCPrioritizedTextureManager::prioritizeTextures() +{ + TRACE_EVENT0("cc", "CCPrioritizedTextureManager::prioritizeTextures"); + +#if !ASSERT_DISABLED + assertInvariants(); +#endif + + // Sorting textures in this function could be replaced by a slightly + // modified O(n) quick-select to partition textures rather than + // sort them (if performance of the sort becomes an issue). + + TextureVector& sortedTextures = m_tempTextureVector; + BackingVector& sortedBackings = m_tempBackingVector; + sortedTextures.clear(); + sortedBackings.clear(); + + // Copy all textures into a vector and sort them. + for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); ++it) + sortedTextures.append(*it); + std::sort(sortedTextures.begin(), sortedTextures.end(), compareTextures); + + m_memoryAvailableBytes = m_maxMemoryLimitBytes; + m_priorityCutoff = CCPriorityCalculator::lowestPriority(); + size_t memoryBytes = 0; + for (TextureVector::iterator it = sortedTextures.begin(); it != sortedTextures.end(); ++it) { + if ((*it)->requestPriority() == CCPriorityCalculator::lowestPriority()) + break; + + if ((*it)->isSelfManaged()) { + // Account for self-managed memory immediately by reducing the memory + // available (since it never gets acquired). + size_t newMemoryBytes = memoryBytes + (*it)->bytes(); + if (newMemoryBytes > m_memoryAvailableBytes) { + m_priorityCutoff = (*it)->requestPriority(); + m_memoryAvailableBytes = memoryBytes; + break; + } + m_memoryAvailableBytes -= (*it)->bytes(); + } else { + size_t newMemoryBytes = memoryBytes + (*it)->bytes(); + if (newMemoryBytes > m_memoryAvailableBytes) { + m_priorityCutoff = (*it)->requestPriority(); + break; + } + memoryBytes = newMemoryBytes; + } + } + + // Only allow textures if they are higher than the cutoff. All textures + // of the same priority are accepted or rejected together, rather than + // being partially allowed randomly. + m_memoryAboveCutoffBytes = 0; + for (TextureVector::iterator it = sortedTextures.begin(); it != sortedTextures.end(); ++it) { + bool isAbovePriorityCutoff = CCPriorityCalculator::priorityIsHigher((*it)->requestPriority(), m_priorityCutoff); + (*it)->setAbovePriorityCutoff(isAbovePriorityCutoff); + if (isAbovePriorityCutoff && !(*it)->isSelfManaged()) + m_memoryAboveCutoffBytes += (*it)->bytes(); + } + ASSERT(m_memoryAboveCutoffBytes <= m_memoryAvailableBytes); + + // Put backings in eviction/recycling order. + for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) + sortedBackings.append(*it); + std::sort(sortedBackings.begin(), sortedBackings.end(), compareBackings); + + for (BackingVector::iterator it = sortedBackings.begin(); it != sortedBackings.end(); ++it) { + m_backings.remove(*it); + m_backings.add(*it); + } + + sortedTextures.clear(); + sortedBackings.clear(); + +#if !ASSERT_DISABLED + assertInvariants(); + ASSERT(memoryAboveCutoffBytes() <= maxMemoryLimitBytes()); +#endif +} + +void CCPrioritizedTextureManager::clearPriorities() +{ + for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); ++it) { + // FIXME: We should remove this and just set all priorities to + // CCPriorityCalculator::lowestPriority() once we have priorities + // for all textures (we can't currently calculate distances for + // off-screen textures). + (*it)->setRequestPriority(CCPriorityCalculator::lingeringPriority((*it)->requestPriority())); + } +} + +bool CCPrioritizedTextureManager::requestLate(CCPrioritizedTexture* texture) +{ + // This is already above cutoff, so don't double count it's memory below. + if (texture->isAbovePriorityCutoff()) + return true; + + if (CCPriorityCalculator::priorityIsLower(texture->requestPriority(), m_priorityCutoff)) + return false; + + size_t newMemoryBytes = m_memoryAboveCutoffBytes + texture->bytes(); + if (newMemoryBytes > m_memoryAvailableBytes) + return false; + + m_memoryAboveCutoffBytes = newMemoryBytes; + texture->setAbovePriorityCutoff(true); + if (texture->backing()) { + m_backings.remove(texture->backing()); + m_backings.add(texture->backing()); + } + return true; +} + +void CCPrioritizedTextureManager::acquireBackingTextureIfNeeded(CCPrioritizedTexture* texture, CCResourceProvider* resourceProvider) +{ + ASSERT(!texture->isSelfManaged()); + ASSERT(texture->isAbovePriorityCutoff()); + if (texture->backing() || !texture->isAbovePriorityCutoff()) + return; + + // Find a backing below, by either recycling or allocating. + CCPrioritizedTexture::Backing* backing = 0; + + // First try to recycle + for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) { + if ((*it)->owner() && (*it)->owner()->isAbovePriorityCutoff()) + break; + if ((*it)->size() == texture->size() && (*it)->format() == texture->format()) { + backing = (*it); + break; + } + } + + // Otherwise reduce memory and just allocate a new backing texures. + if (!backing) { + reduceMemory(m_memoryAvailableBytes - texture->bytes(), resourceProvider); + backing = createBacking(texture->size(), texture->format(), resourceProvider); + } + + // Move the used backing texture to the end of the eviction list. + if (backing->owner()) + backing->owner()->unlink(); + texture->link(backing); + m_backings.remove(backing); + m_backings.add(backing); +} + +void CCPrioritizedTextureManager::reduceMemory(size_t limitBytes, CCResourceProvider* resourceProvider) +{ + if (memoryUseBytes() <= limitBytes) + return; + // Destroy backings until we are below the limit, + // or until all backings remaining are above the cutoff. + while (memoryUseBytes() > limitBytes && m_backings.size() > 0) { + BackingSet::iterator it = m_backings.begin(); + if ((*it)->owner() && (*it)->owner()->isAbovePriorityCutoff()) + break; + destroyBacking((*it), resourceProvider); + } +} + +void CCPrioritizedTextureManager::reduceMemory(CCResourceProvider* resourceProvider) +{ + reduceMemory(m_memoryAvailableBytes, resourceProvider); + ASSERT(memoryUseBytes() <= maxMemoryLimitBytes()); + + // We currently collect backings from deleted textures for later recycling. + // However, if we do that forever we will always use the max limit even if + // we really need very little memory. This should probably be solved by reducing the + // limit externally, but until then this just does some "clean up" of unused + // backing textures (any more than 10%). + size_t wastedMemory = 0; + for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) { + if ((*it)->owner()) + break; + wastedMemory += (*it)->bytes(); + } + size_t tenPercentOfMemory = m_memoryAvailableBytes / 10; + if (wastedMemory <= tenPercentOfMemory) + return; + reduceMemory(memoryUseBytes() - (wastedMemory - tenPercentOfMemory), resourceProvider); +} + +void CCPrioritizedTextureManager::clearAllMemory(CCResourceProvider* resourceProvider) +{ + // Unlink and destroy all backing textures. + while (m_backings.size() > 0) { + BackingSet::iterator it = m_backings.begin(); + if ((*it)->owner()) + (*it)->owner()->unlink(); + destroyBacking((*it), resourceProvider); + } +} + +void CCPrioritizedTextureManager::allBackingTexturesWereDeleted() +{ + // Same as clearAllMemory, except all our textures were already + // deleted externally, so we don't delete them. Passing no + // resourceProvider results in leaking the (now invalid) texture ids. + clearAllMemory(0); +} + +void CCPrioritizedTextureManager::registerTexture(CCPrioritizedTexture* texture) +{ + ASSERT(texture); + ASSERT(!texture->textureManager()); + ASSERT(!texture->backing()); + ASSERT(m_textures.find(texture) == m_textures.end()); + + texture->setManagerInternal(this); + m_textures.add(texture); + +} + +void CCPrioritizedTextureManager::unregisterTexture(CCPrioritizedTexture* texture) +{ + ASSERT(texture); + ASSERT(m_textures.find(texture) != m_textures.end()); + + returnBackingTexture(texture); + texture->setManagerInternal(0); + m_textures.remove(texture); + texture->setAbovePriorityCutoff(false); +} + + +void CCPrioritizedTextureManager::returnBackingTexture(CCPrioritizedTexture* texture) +{ + if (texture->backing()) { + // Move the backing texture to the front for eviction/recycling and unlink it. + m_backings.remove(texture->backing()); + m_backings.insertBefore(m_backings.begin(), texture->backing()); + texture->unlink(); + } +} + +CCPrioritizedTexture::Backing* CCPrioritizedTextureManager::createBacking(IntSize size, GC3Denum format, CCResourceProvider* resourceProvider) +{ + ASSERT(resourceProvider); + + CCResourceProvider::ResourceId resourceId = resourceProvider->createResource(m_pool, size, format, CCResourceProvider::TextureUsageAny); + CCPrioritizedTexture::Backing* backing = new CCPrioritizedTexture::Backing(resourceId, size, format); + m_memoryUseBytes += backing->bytes(); + // Put backing texture at the front for eviction, since it isn't in use yet. + m_backings.insertBefore(m_backings.begin(), backing); + return backing; +} + +void CCPrioritizedTextureManager::destroyBacking(CCPrioritizedTexture::Backing* backing, CCResourceProvider* resourceProvider) +{ + ASSERT(backing); + ASSERT(!backing->owner() || !backing->owner()->isAbovePriorityCutoff()); + ASSERT(!backing->owner() || !backing->owner()->isSelfManaged()); + ASSERT(m_backings.find(backing) != m_backings.end()); + + if (resourceProvider) + resourceProvider->deleteResource(backing->id()); + if (backing->owner()) + backing->owner()->unlink(); + m_memoryUseBytes -= backing->bytes(); + m_backings.remove(backing); + + delete backing; +} + + +#if !ASSERT_DISABLED +void CCPrioritizedTextureManager::assertInvariants() +{ + // If we hit any of these asserts, there is a bug in this class. To see + // where the bug is, call this function at the beginning and end of + // every public function. + + // Backings/textures must be doubly-linked and only to other backings/textures in this manager. + for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) { + if ((*it)->owner()) { + ASSERT(m_textures.find((*it)->owner()) != m_textures.end()); + ASSERT((*it)->owner()->backing() == (*it)); + } + } + for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); ++it) { + if ((*it)->backing()) { + ASSERT(m_backings.find((*it)->backing()) != m_backings.end()); + ASSERT((*it)->backing()->owner() == (*it)); + } + } + + // At all times, backings that can be evicted must always come before + // backings that can't be evicted in the backing texture list (otherwise + // reduceMemory will not find all textures available for eviction/recycling). + bool reachedProtected = false; + for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) { + if ((*it)->owner() && (*it)->owner()->isAbovePriorityCutoff()) + reachedProtected = true; + if (reachedProtected) + ASSERT((*it)->owner() && (*it)->owner()->isAbovePriorityCutoff()); + } +} +#endif + + +} // namespace WebCore diff --git a/cc/CCPrioritizedTextureManager.h b/cc/CCPrioritizedTextureManager.h new file mode 100644 index 0000000..42a10c8 --- /dev/null +++ b/cc/CCPrioritizedTextureManager.h @@ -0,0 +1,119 @@ +// Copyright 2012 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. + +#ifndef CCPrioritizedTextureManager_h +#define CCPrioritizedTextureManager_h + +#include "CCPrioritizedTexture.h" +#include "CCPriorityCalculator.h" +#include "CCTexture.h" +#include "GraphicsContext3D.h" +#include "IntRect.h" +#include "IntSize.h" +#include <wtf/HashSet.h> +#include <wtf/ListHashSet.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class CCPrioritizedTexture; +class CCPriorityCalculator; + +class CCPrioritizedTextureManager { + WTF_MAKE_NONCOPYABLE(CCPrioritizedTextureManager); +public: + static PassOwnPtr<CCPrioritizedTextureManager> create(size_t maxMemoryLimitBytes, int maxTextureSize, int pool) + { + return adoptPtr(new CCPrioritizedTextureManager(maxMemoryLimitBytes, maxTextureSize, pool)); + } + PassOwnPtr<CCPrioritizedTexture> createTexture(IntSize size, GC3Denum format) + { + return adoptPtr(new CCPrioritizedTexture(this, size, format)); + } + ~CCPrioritizedTextureManager(); + + // FIXME (http://crbug.com/137094): This 64MB default is a straggler from the + // old texture manager and is just to give us a default memory allocation before + // we get a callback from the GPU memory manager. We should probaby either: + // - wait for the callback before rendering anything instead + // - push this into the GPU memory manager somehow. + static size_t defaultMemoryAllocationLimit() { return 64 * 1024 * 1024; } + + // memoryUseBytes() describes the number of bytes used by existing allocated textures. + // memoryAboveCutoffBytes() describes the number of bytes that would be used if all + // textures that are above the cutoff were allocated. + // memoryUseBytes() <= memoryAboveCutoffBytes() should always be true. + size_t memoryUseBytes() const { return m_memoryUseBytes; } + size_t memoryAboveCutoffBytes() const { return m_memoryAboveCutoffBytes; } + size_t memoryForSelfManagedTextures() const { return m_maxMemoryLimitBytes - m_memoryAvailableBytes; } + + void setMaxMemoryLimitBytes(size_t bytes) { m_maxMemoryLimitBytes = bytes; } + size_t maxMemoryLimitBytes() const { return m_maxMemoryLimitBytes; } + + void prioritizeTextures(); + void clearPriorities(); + + bool requestLate(CCPrioritizedTexture*); + + void reduceMemory(CCResourceProvider*); + void clearAllMemory(CCResourceProvider*); + void allBackingTexturesWereDeleted(); + + void acquireBackingTextureIfNeeded(CCPrioritizedTexture*, CCResourceProvider*); + + void registerTexture(CCPrioritizedTexture*); + void unregisterTexture(CCPrioritizedTexture*); + void returnBackingTexture(CCPrioritizedTexture*); + +#if !ASSERT_DISABLED + void assertInvariants(); +#endif + +private: + // Compare textures. Highest priority first. + static inline bool compareTextures(CCPrioritizedTexture* a, CCPrioritizedTexture* b) + { + if (a->requestPriority() == b->requestPriority()) + return a < b; + return CCPriorityCalculator::priorityIsHigher(a->requestPriority(), b->requestPriority()); + } + // Compare backings. Lowest priority first. + static inline bool compareBackings(CCPrioritizedTexture::Backing* a, CCPrioritizedTexture::Backing* b) + { + int priorityA = a->owner() ? a->owner()->requestPriority() : CCPriorityCalculator::lowestPriority(); + int priorityB = b->owner() ? b->owner()->requestPriority() : CCPriorityCalculator::lowestPriority(); + if (priorityA == priorityB) + return a < b; + return CCPriorityCalculator::priorityIsLower(priorityA, priorityB); + } + + CCPrioritizedTextureManager(size_t maxMemoryLimitBytes, int maxTextureSize, int pool); + + void reduceMemory(size_t limit, CCResourceProvider*); + + CCPrioritizedTexture::Backing* createBacking(IntSize, GC3Denum format, CCResourceProvider*); + void destroyBacking(CCPrioritizedTexture::Backing*, CCResourceProvider*); + + size_t m_maxMemoryLimitBytes; + unsigned m_priorityCutoff; + size_t m_memoryUseBytes; + size_t m_memoryAboveCutoffBytes; + size_t m_memoryAvailableBytes; + int m_pool; + + typedef HashSet<CCPrioritizedTexture*> TextureSet; + typedef ListHashSet<CCPrioritizedTexture::Backing*> BackingSet; + typedef Vector<CCPrioritizedTexture*> TextureVector; + typedef Vector<CCPrioritizedTexture::Backing*> BackingVector; + + TextureSet m_textures; + BackingSet m_backings; + + TextureVector m_tempTextureVector; + BackingVector m_tempBackingVector; +}; + +} // WebCore + +#endif diff --git a/cc/CCPrioritizedTextureTest.cpp b/cc/CCPrioritizedTextureTest.cpp new file mode 100644 index 0000000..0594d6b --- /dev/null +++ b/cc/CCPrioritizedTextureTest.cpp @@ -0,0 +1,456 @@ +// Copyright 2012 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 "CCPrioritizedTexture.h" + +#include "CCPrioritizedTextureManager.h" +#include "CCSingleThreadProxy.h" // For DebugScopedSetImplThread +#include "CCTexture.h" +#include "CCTiledLayerTestCommon.h" +#include "FakeCCGraphicsContext.h" +#include <gtest/gtest.h> + +using namespace WebCore; +using namespace WebKitTests; +using namespace WTF; + +namespace { + +class CCPrioritizedTextureTest : public testing::Test { +public: + CCPrioritizedTextureTest() + : m_textureSize(256, 256) + , m_textureFormat(GraphicsContext3D::RGBA) + , m_context(WebKit::createFakeCCGraphicsContext()) + { + DebugScopedSetImplThread implThread; + m_resourceProvider = CCResourceProvider::create(m_context.get()); + } + + virtual ~CCPrioritizedTextureTest() + { + DebugScopedSetImplThread implThread; + m_resourceProvider.clear(); + } + + size_t texturesMemorySize(size_t textureCount) + { + return CCTexture::memorySizeBytes(m_textureSize, m_textureFormat) * textureCount; + } + + PassOwnPtr<CCPrioritizedTextureManager> createManager(size_t maxTextures) + { + return CCPrioritizedTextureManager::create(texturesMemorySize(maxTextures), 1024, 0); + } + + bool validateTexture(OwnPtr<CCPrioritizedTexture>& texture, bool requestLate) + { + DebugScopedSetImplThread implThread; +#if !ASSERT_DISABLED + texture->textureManager()->assertInvariants(); +#endif + if (requestLate) + texture->requestLate(); + bool success = texture->canAcquireBackingTexture(); + if (success) + texture->acquireBackingTexture(resourceProvider()); + return success; + } + + CCResourceProvider* resourceProvider() + { + return m_resourceProvider.get(); + } + +protected: + const IntSize m_textureSize; + const GC3Denum m_textureFormat; + OwnPtr<CCGraphicsContext> m_context; + OwnPtr<CCResourceProvider> m_resourceProvider; +}; + +TEST_F(CCPrioritizedTextureTest, requestTextureExceedingMaxLimit) +{ + const size_t maxTextures = 8; + OwnPtr<CCPrioritizedTextureManager> textureManager = createManager(maxTextures); + + // Create textures for double our memory limit. + OwnPtr<CCPrioritizedTexture> textures[maxTextures*2]; + + for (size_t i = 0; i < maxTextures*2; ++i) + textures[i] = textureManager->createTexture(m_textureSize, m_textureFormat); + + // Set decreasing priorities + for (size_t i = 0; i < maxTextures*2; ++i) + textures[i]->setRequestPriority(100 + i); + + // Only lower half should be available. + textureManager->prioritizeTextures(); + EXPECT_TRUE(validateTexture(textures[0], false)); + EXPECT_TRUE(validateTexture(textures[7], false)); + EXPECT_FALSE(validateTexture(textures[8], false)); + EXPECT_FALSE(validateTexture(textures[15], false)); + + // Set increasing priorities + for (size_t i = 0; i < maxTextures*2; ++i) + textures[i]->setRequestPriority(100 - i); + + // Only upper half should be available. + textureManager->prioritizeTextures(); + EXPECT_FALSE(validateTexture(textures[0], false)); + EXPECT_FALSE(validateTexture(textures[7], false)); + EXPECT_TRUE(validateTexture(textures[8], false)); + EXPECT_TRUE(validateTexture(textures[15], false)); + + EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->memoryAboveCutoffBytes()); + EXPECT_LE(textureManager->memoryUseBytes(), textureManager->memoryAboveCutoffBytes()); + + DebugScopedSetImplThread implThread; + textureManager->clearAllMemory(resourceProvider()); +} + +TEST_F(CCPrioritizedTextureTest, changeMemoryLimits) +{ + const size_t maxTextures = 8; + OwnPtr<CCPrioritizedTextureManager> textureManager = createManager(maxTextures); + OwnPtr<CCPrioritizedTexture> textures[maxTextures]; + + for (size_t i = 0; i < maxTextures; ++i) + textures[i] = textureManager->createTexture(m_textureSize, m_textureFormat); + for (size_t i = 0; i < maxTextures; ++i) + textures[i]->setRequestPriority(100 + i); + + // Set max limit to 8 textures + textureManager->setMaxMemoryLimitBytes(texturesMemorySize(8)); + textureManager->prioritizeTextures(); + for (size_t i = 0; i < maxTextures; ++i) + validateTexture(textures[i], false); + { + DebugScopedSetImplThread implThread; + textureManager->reduceMemory(resourceProvider()); + } + + EXPECT_EQ(texturesMemorySize(8), textureManager->memoryAboveCutoffBytes()); + EXPECT_LE(textureManager->memoryUseBytes(), textureManager->memoryAboveCutoffBytes()); + + // Set max limit to 5 textures + textureManager->setMaxMemoryLimitBytes(texturesMemorySize(5)); + textureManager->prioritizeTextures(); + for (size_t i = 0; i < maxTextures; ++i) + EXPECT_EQ(validateTexture(textures[i], false), i < 5); + { + DebugScopedSetImplThread implThread; + textureManager->reduceMemory(resourceProvider()); + } + + EXPECT_EQ(texturesMemorySize(5), textureManager->memoryAboveCutoffBytes()); + EXPECT_LE(textureManager->memoryUseBytes(), textureManager->memoryAboveCutoffBytes()); + + // Set max limit to 4 textures + textureManager->setMaxMemoryLimitBytes(texturesMemorySize(4)); + textureManager->prioritizeTextures(); + for (size_t i = 0; i < maxTextures; ++i) + EXPECT_EQ(validateTexture(textures[i], false), i < 4); + { + DebugScopedSetImplThread implThread; + textureManager->reduceMemory(resourceProvider()); + } + + EXPECT_EQ(texturesMemorySize(4), textureManager->memoryAboveCutoffBytes()); + EXPECT_LE(textureManager->memoryUseBytes(), textureManager->memoryAboveCutoffBytes()); + + DebugScopedSetImplThread implThread; + textureManager->clearAllMemory(resourceProvider()); +} + +TEST_F(CCPrioritizedTextureTest, textureManagerPartialUpdateTextures) +{ + const size_t maxTextures = 4; + const size_t numTextures = 4; + OwnPtr<CCPrioritizedTextureManager> textureManager = createManager(maxTextures); + OwnPtr<CCPrioritizedTexture> textures[numTextures]; + OwnPtr<CCPrioritizedTexture> moreTextures[numTextures]; + + for (size_t i = 0; i < numTextures; ++i) { + textures[i] = textureManager->createTexture(m_textureSize, m_textureFormat); + moreTextures[i] = textureManager->createTexture(m_textureSize, m_textureFormat); + } + + for (size_t i = 0; i < numTextures; ++i) + textures[i]->setRequestPriority(200 + i); + textureManager->prioritizeTextures(); + + // Allocate textures which are currently high priority. + EXPECT_TRUE(validateTexture(textures[0], false)); + EXPECT_TRUE(validateTexture(textures[1], false)); + EXPECT_TRUE(validateTexture(textures[2], false)); + EXPECT_TRUE(validateTexture(textures[3], false)); + + EXPECT_TRUE(textures[0]->haveBackingTexture()); + EXPECT_TRUE(textures[1]->haveBackingTexture()); + EXPECT_TRUE(textures[2]->haveBackingTexture()); + EXPECT_TRUE(textures[3]->haveBackingTexture()); + + for (size_t i = 0; i < numTextures; ++i) + moreTextures[i]->setRequestPriority(100 + i); + textureManager->prioritizeTextures(); + + // Textures are now below cutoff. + EXPECT_FALSE(validateTexture(textures[0], false)); + EXPECT_FALSE(validateTexture(textures[1], false)); + EXPECT_FALSE(validateTexture(textures[2], false)); + EXPECT_FALSE(validateTexture(textures[3], false)); + + // But they are still valid to use. + EXPECT_TRUE(textures[0]->haveBackingTexture()); + EXPECT_TRUE(textures[1]->haveBackingTexture()); + EXPECT_TRUE(textures[2]->haveBackingTexture()); + EXPECT_TRUE(textures[3]->haveBackingTexture()); + + // Higher priority textures are finally needed. + EXPECT_TRUE(validateTexture(moreTextures[0], false)); + EXPECT_TRUE(validateTexture(moreTextures[1], false)); + EXPECT_TRUE(validateTexture(moreTextures[2], false)); + EXPECT_TRUE(validateTexture(moreTextures[3], false)); + + // Lower priority have been fully evicted. + EXPECT_FALSE(textures[0]->haveBackingTexture()); + EXPECT_FALSE(textures[1]->haveBackingTexture()); + EXPECT_FALSE(textures[2]->haveBackingTexture()); + EXPECT_FALSE(textures[3]->haveBackingTexture()); + + DebugScopedSetImplThread implThread; + textureManager->clearAllMemory(resourceProvider()); +} + +TEST_F(CCPrioritizedTextureTest, textureManagerPrioritiesAreEqual) +{ + const size_t maxTextures = 16; + OwnPtr<CCPrioritizedTextureManager> textureManager = createManager(maxTextures); + OwnPtr<CCPrioritizedTexture> textures[maxTextures]; + + for (size_t i = 0; i < maxTextures; ++i) + textures[i] = textureManager->createTexture(m_textureSize, m_textureFormat); + + // All 16 textures have the same priority except 2 higher priority. + for (size_t i = 0; i < maxTextures; ++i) + textures[i]->setRequestPriority(100); + textures[0]->setRequestPriority(99); + textures[1]->setRequestPriority(99); + + // Set max limit to 8 textures + textureManager->setMaxMemoryLimitBytes(texturesMemorySize(8)); + textureManager->prioritizeTextures(); + + // The two high priority textures should be available, others should not. + for (size_t i = 0; i < 2; ++i) + EXPECT_TRUE(validateTexture(textures[i], false)); + for (size_t i = 2; i < maxTextures; ++i) + EXPECT_FALSE(validateTexture(textures[i], false)); + EXPECT_EQ(texturesMemorySize(2), textureManager->memoryAboveCutoffBytes()); + EXPECT_LE(textureManager->memoryUseBytes(), textureManager->memoryAboveCutoffBytes()); + + // Manually reserving textures should only succeed on the higher priority textures, + // and on remaining textures up to the memory limit. + for (size_t i = 0; i < 8; i++) + EXPECT_TRUE(validateTexture(textures[i], true)); + for (size_t i = 9; i < maxTextures; i++) + EXPECT_FALSE(validateTexture(textures[i], true)); + EXPECT_EQ(texturesMemorySize(8), textureManager->memoryAboveCutoffBytes()); + EXPECT_LE(textureManager->memoryUseBytes(), textureManager->memoryAboveCutoffBytes()); + + DebugScopedSetImplThread implThread; + textureManager->clearAllMemory(resourceProvider()); +} + +TEST_F(CCPrioritizedTextureTest, textureManagerDestroyedFirst) +{ + OwnPtr<CCPrioritizedTextureManager> textureManager = createManager(1); + OwnPtr<CCPrioritizedTexture> texture = textureManager->createTexture(m_textureSize, m_textureFormat); + + // Texture is initially invalid, but it will become available. + EXPECT_FALSE(texture->haveBackingTexture()); + + texture->setRequestPriority(100); + textureManager->prioritizeTextures(); + + EXPECT_TRUE(validateTexture(texture, false)); + EXPECT_TRUE(texture->canAcquireBackingTexture()); + EXPECT_TRUE(texture->haveBackingTexture()); + + { + DebugScopedSetImplThread implThread; + textureManager->clearAllMemory(resourceProvider()); + } + textureManager.clear(); + + EXPECT_FALSE(texture->canAcquireBackingTexture()); + EXPECT_FALSE(texture->haveBackingTexture()); +} + +TEST_F(CCPrioritizedTextureTest, textureMovedToNewManager) +{ + OwnPtr<CCPrioritizedTextureManager> textureManagerOne = createManager(1); + OwnPtr<CCPrioritizedTextureManager> textureManagerTwo = createManager(1); + OwnPtr<CCPrioritizedTexture> texture = textureManagerOne->createTexture(m_textureSize, m_textureFormat); + + // Texture is initially invalid, but it will become available. + EXPECT_FALSE(texture->haveBackingTexture()); + + texture->setRequestPriority(100); + textureManagerOne->prioritizeTextures(); + + EXPECT_TRUE(validateTexture(texture, false)); + EXPECT_TRUE(texture->canAcquireBackingTexture()); + EXPECT_TRUE(texture->haveBackingTexture()); + + texture->setTextureManager(0); + + { + DebugScopedSetImplThread implThread; + textureManagerOne->clearAllMemory(resourceProvider()); + } + textureManagerOne.clear(); + + EXPECT_FALSE(texture->canAcquireBackingTexture()); + EXPECT_FALSE(texture->haveBackingTexture()); + + texture->setTextureManager(textureManagerTwo.get()); + + textureManagerTwo->prioritizeTextures(); + + EXPECT_TRUE(validateTexture(texture, false)); + EXPECT_TRUE(texture->canAcquireBackingTexture()); + EXPECT_TRUE(texture->haveBackingTexture()); + + DebugScopedSetImplThread implThread; + textureManagerTwo->clearAllMemory(resourceProvider()); +} + +TEST_F(CCPrioritizedTextureTest, renderSurfacesReduceMemoryAvailableOutsideRootSurface) +{ + const size_t maxTextures = 8; + OwnPtr<CCPrioritizedTextureManager> textureManager = createManager(maxTextures); + + // Half of the memory is taken by surfaces (with high priority place-holder) + OwnPtr<CCPrioritizedTexture> renderSurfacePlaceHolder = textureManager->createTexture(m_textureSize, m_textureFormat); + renderSurfacePlaceHolder->setToSelfManagedMemoryPlaceholder(texturesMemorySize(4)); + renderSurfacePlaceHolder->setRequestPriority(CCPriorityCalculator::renderSurfacePriority()); + + // Create textures to fill our memory limit. + OwnPtr<CCPrioritizedTexture> textures[maxTextures]; + + for (size_t i = 0; i < maxTextures; ++i) + textures[i] = textureManager->createTexture(m_textureSize, m_textureFormat); + + // Set decreasing non-visible priorities outside root surface. + for (size_t i = 0; i < maxTextures; ++i) + textures[i]->setRequestPriority(100 + i); + + // Only lower half should be available. + textureManager->prioritizeTextures(); + EXPECT_TRUE(validateTexture(textures[0], false)); + EXPECT_TRUE(validateTexture(textures[3], false)); + EXPECT_FALSE(validateTexture(textures[4], false)); + EXPECT_FALSE(validateTexture(textures[7], false)); + + // Set increasing non-visible priorities outside root surface. + for (size_t i = 0; i < maxTextures; ++i) + textures[i]->setRequestPriority(100 - i); + + // Only upper half should be available. + textureManager->prioritizeTextures(); + EXPECT_FALSE(validateTexture(textures[0], false)); + EXPECT_FALSE(validateTexture(textures[3], false)); + EXPECT_TRUE(validateTexture(textures[4], false)); + EXPECT_TRUE(validateTexture(textures[7], false)); + + EXPECT_EQ(texturesMemorySize(4), textureManager->memoryAboveCutoffBytes()); + EXPECT_EQ(texturesMemorySize(4), textureManager->memoryForSelfManagedTextures()); + EXPECT_LE(textureManager->memoryUseBytes(), textureManager->memoryAboveCutoffBytes()); + + DebugScopedSetImplThread implThread; + textureManager->clearAllMemory(resourceProvider()); +} + +TEST_F(CCPrioritizedTextureTest, renderSurfacesReduceMemoryAvailableForRequestLate) +{ + const size_t maxTextures = 8; + OwnPtr<CCPrioritizedTextureManager> textureManager = createManager(maxTextures); + + // Half of the memory is taken by surfaces (with high priority place-holder) + OwnPtr<CCPrioritizedTexture> renderSurfacePlaceHolder = textureManager->createTexture(m_textureSize, m_textureFormat); + renderSurfacePlaceHolder->setToSelfManagedMemoryPlaceholder(texturesMemorySize(4)); + renderSurfacePlaceHolder->setRequestPriority(CCPriorityCalculator::renderSurfacePriority()); + + // Create textures to fill our memory limit. + OwnPtr<CCPrioritizedTexture> textures[maxTextures]; + + for (size_t i = 0; i < maxTextures; ++i) + textures[i] = textureManager->createTexture(m_textureSize, m_textureFormat); + + // Set equal priorities. + for (size_t i = 0; i < maxTextures; ++i) + textures[i]->setRequestPriority(100); + + // The first four to be requested late will be available. + textureManager->prioritizeTextures(); + for (unsigned i = 0; i < maxTextures; ++i) + EXPECT_FALSE(validateTexture(textures[i], false)); + for (unsigned i = 0; i < maxTextures; i += 2) + EXPECT_TRUE(validateTexture(textures[i], true)); + for (unsigned i = 1; i < maxTextures; i += 2) + EXPECT_FALSE(validateTexture(textures[i], true)); + + EXPECT_EQ(texturesMemorySize(4), textureManager->memoryAboveCutoffBytes()); + EXPECT_EQ(texturesMemorySize(4), textureManager->memoryForSelfManagedTextures()); + EXPECT_LE(textureManager->memoryUseBytes(), textureManager->memoryAboveCutoffBytes()); + + DebugScopedSetImplThread implThread; + textureManager->clearAllMemory(resourceProvider()); +} + +TEST_F(CCPrioritizedTextureTest, whenRenderSurfaceNotAvailableTexturesAlsoNotAvailable) +{ + const size_t maxTextures = 8; + OwnPtr<CCPrioritizedTextureManager> textureManager = createManager(maxTextures); + + // Half of the memory is taken by surfaces (with high priority place-holder) + OwnPtr<CCPrioritizedTexture> renderSurfacePlaceHolder = textureManager->createTexture(m_textureSize, m_textureFormat); + renderSurfacePlaceHolder->setToSelfManagedMemoryPlaceholder(texturesMemorySize(4)); + renderSurfacePlaceHolder->setRequestPriority(CCPriorityCalculator::renderSurfacePriority()); + + // Create textures to fill our memory limit. + OwnPtr<CCPrioritizedTexture> textures[maxTextures]; + + for (size_t i = 0; i < maxTextures; ++i) + textures[i] = textureManager->createTexture(m_textureSize, m_textureFormat); + + // Set 6 visible textures in the root surface, and 2 in a child surface. + for (size_t i = 0; i < 6; ++i) + textures[i]->setRequestPriority(CCPriorityCalculator::visiblePriority(true)); + for (size_t i = 6; i < 8; ++i) + textures[i]->setRequestPriority(CCPriorityCalculator::visiblePriority(false)); + + textureManager->prioritizeTextures(); + + // Unable to requestLate textures in the child surface. + EXPECT_FALSE(validateTexture(textures[6], true)); + EXPECT_FALSE(validateTexture(textures[7], true)); + + // Root surface textures are valid. + for (size_t i = 0; i < 6; ++i) + EXPECT_TRUE(validateTexture(textures[i], false)); + + EXPECT_EQ(texturesMemorySize(6), textureManager->memoryAboveCutoffBytes()); + EXPECT_EQ(texturesMemorySize(2), textureManager->memoryForSelfManagedTextures()); + EXPECT_LE(textureManager->memoryUseBytes(), textureManager->memoryAboveCutoffBytes()); + + DebugScopedSetImplThread implThread; + textureManager->clearAllMemory(resourceProvider()); +} + +} // namespace diff --git a/cc/CCPriorityCalculator.cpp b/cc/CCPriorityCalculator.cpp new file mode 100644 index 0000000..7e769f7 --- /dev/null +++ b/cc/CCPriorityCalculator.cpp @@ -0,0 +1,72 @@ +// Copyright 2012 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 "CCPriorityCalculator.h" + +using namespace std; + +namespace WebCore { + +// static +int CCPriorityCalculator::uiPriority(bool drawsToRootSurface) +{ + return drawsToRootSurface ? -1 : 2; +} + +// static +int CCPriorityCalculator::visiblePriority(bool drawsToRootSurface) +{ + return drawsToRootSurface ? 0 : 3; +} + +// static +int CCPriorityCalculator::renderSurfacePriority() +{ + return 1; +} + +// static +int CCPriorityCalculator::lingeringPriority(int previousPriority) +{ + // FIXME: We should remove this once we have priorities for all + // textures (we can't currently calculate distances for + // off-screen textures). + int lingeringPriority = 1000000; + return min(numeric_limits<int>::max() - 1, + max(lingeringPriority, previousPriority)) + 1; +} + +namespace { +unsigned manhattanDistance(const IntRect& a, const IntRect& b) +{ + IntRect c = unionRect(a, b); + int x = max(0, c.width() - a.width() - b.width() + 1); + int y = max(0, c.height() - a.height() - b.height() + 1); + return (x + y); +} +} + +int CCPriorityCalculator::priorityFromDistance(const IntRect& visibleRect, const IntRect& textureRect, bool drawsToRootSurface) const +{ + unsigned distance = manhattanDistance(visibleRect, textureRect); + if (!distance) + return visiblePriority(drawsToRootSurface); + return visiblePriority(false) + distance; +} + +int CCPriorityCalculator::priorityFromDistance(unsigned pixels, bool drawsToRootSurface) const +{ + if (!pixels) + return visiblePriority(drawsToRootSurface); + return visiblePriority(false) + pixels; +} + +int CCPriorityCalculator::priorityFromVisibility(bool visible, bool drawsToRootSurface) const +{ + return visible ? visiblePriority(drawsToRootSurface) : lowestPriority(); +} + +} // WebCore diff --git a/cc/CCPriorityCalculator.h b/cc/CCPriorityCalculator.h new file mode 100644 index 0000000..91ad766 --- /dev/null +++ b/cc/CCPriorityCalculator.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef CCPriorityCalculator_h +#define CCPriorityCalculator_h + +#include "GraphicsContext3D.h" +#include "IntRect.h" +#include "IntSize.h" + +namespace WebCore { + +class CCPriorityCalculator { +public: + static int uiPriority(bool drawsToRootSurface); + static int visiblePriority(bool drawsToRootSurface); + static int renderSurfacePriority(); + static int lingeringPriority(int previousPriority); + int priorityFromDistance(const IntRect& visibleRect, const IntRect& textureRect, bool drawsToRootSurface) const; + int priorityFromDistance(unsigned pixels, bool drawsToRootSurface) const; + int priorityFromVisibility(bool visible, bool drawsToRootSurface) const; + + static inline int highestPriority() { return std::numeric_limits<int>::min(); } + static inline int lowestPriority() { return std::numeric_limits<int>::max(); } + static inline bool priorityIsLower(int a, int b) { return a > b; } + static inline bool priorityIsHigher(int a, int b) { return a < b; } + static inline bool maxPriority(int a, int b) { return priorityIsHigher(a, b) ? a : b; } +}; + +} + +#endif diff --git a/cc/CCProxy.cpp b/cc/CCProxy.cpp new file mode 100644 index 0000000..2e89398 --- /dev/null +++ b/cc/CCProxy.cpp @@ -0,0 +1,106 @@ +// 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 "config.h" + +#include "CCProxy.h" + +#include "CCThreadTask.h" +#include <wtf/MainThread.h> + +using namespace WTF; + +namespace WebCore { + +namespace { +#ifndef NDEBUG +bool implThreadIsOverridden = false; +bool s_isMainThreadBlocked = false; +ThreadIdentifier threadIDOverridenToBeImplThread; +#endif +CCThread* s_mainThread = 0; +CCThread* s_implThread = 0; +} + +void CCProxy::setMainThread(CCThread* thread) +{ + s_mainThread = thread; +} + +CCThread* CCProxy::mainThread() +{ + return s_mainThread; +} + +bool CCProxy::hasImplThread() +{ + return s_implThread; +} + +void CCProxy::setImplThread(CCThread* thread) +{ + s_implThread = thread; +} + +CCThread* CCProxy::implThread() +{ + return s_implThread; +} + +CCThread* CCProxy::currentThread() +{ + ThreadIdentifier currentThreadIdentifier = WTF::currentThread(); + if (s_mainThread && s_mainThread->threadID() == currentThreadIdentifier) + return s_mainThread; + if (s_implThread && s_implThread->threadID() == currentThreadIdentifier) + return s_implThread; + return 0; +} + +#ifndef NDEBUG +bool CCProxy::isMainThread() +{ + ASSERT(s_mainThread); + if (implThreadIsOverridden && WTF::currentThread() == threadIDOverridenToBeImplThread) + return false; + return WTF::currentThread() == s_mainThread->threadID(); +} + +bool CCProxy::isImplThread() +{ + WTF::ThreadIdentifier implThreadID = s_implThread ? s_implThread->threadID() : 0; + if (implThreadIsOverridden && WTF::currentThread() == threadIDOverridenToBeImplThread) + return true; + return WTF::currentThread() == implThreadID; +} + +void CCProxy::setCurrentThreadIsImplThread(bool isImplThread) +{ + implThreadIsOverridden = isImplThread; + if (isImplThread) + threadIDOverridenToBeImplThread = WTF::currentThread(); +} + +bool CCProxy::isMainThreadBlocked() +{ + return s_isMainThreadBlocked; +} + +void CCProxy::setMainThreadBlocked(bool isMainThreadBlocked) +{ + s_isMainThreadBlocked = isMainThreadBlocked; +} +#endif + +CCProxy::CCProxy() +{ + ASSERT(isMainThread()); +} + +CCProxy::~CCProxy() +{ + ASSERT(isMainThread()); +} + +} diff --git a/cc/CCProxy.h b/cc/CCProxy.h new file mode 100644 index 0000000..03913f2 --- /dev/null +++ b/cc/CCProxy.h @@ -0,0 +1,129 @@ +// 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. + +#ifndef CCProxy_h +#define CCProxy_h + +#include "IntRect.h" +#include <public/WebCompositorOutputSurface.h> +#include <wtf/Noncopyable.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class CCThread; +struct CCRenderingStats; +struct RendererCapabilities; + +// Abstract class responsible for proxying commands from the main-thread side of +// the compositor over to the compositor implementation. +class CCProxy { + WTF_MAKE_NONCOPYABLE(CCProxy); +public: + static void setMainThread(CCThread*); + static CCThread* mainThread(); + + static bool hasImplThread(); + static void setImplThread(CCThread*); + static CCThread* implThread(); + + // Returns 0 if the current thread is neither the main thread nor the impl thread. + static CCThread* currentThread(); + + virtual ~CCProxy(); + + virtual bool compositeAndReadback(void *pixels, const IntRect&) = 0; + + virtual void startPageScaleAnimation(const IntSize& targetPosition, bool useAnchor, float scale, double durationSec) = 0; + + virtual void finishAllRendering() = 0; + + virtual bool isStarted() const = 0; + + // Attempts to initialize a context to use for rendering. Returns false if the context could not be created. + // The context will not be used and no frames may be produced until initializeRenderer() is called. + virtual bool initializeContext() = 0; + + // Indicates that the compositing surface associated with our context is ready to use. + virtual void setSurfaceReady() = 0; + + virtual void setVisible(bool) = 0; + + // Attempts to initialize the layer renderer. Returns false if the context isn't usable for compositing. + virtual bool initializeRenderer() = 0; + + // Attempts to recreate the context and layer renderer after a context lost. Returns false if the renderer couldn't be + // reinitialized. + virtual bool recreateContext() = 0; + + virtual int compositorIdentifier() const = 0; + + virtual void implSideRenderingStats(CCRenderingStats&) = 0; + + virtual const RendererCapabilities& rendererCapabilities() const = 0; + + virtual void setNeedsAnimate() = 0; + virtual void setNeedsCommit() = 0; + virtual void setNeedsRedraw() = 0; + + virtual void didAddAnimation() = 0; + + virtual bool commitRequested() const = 0; + + virtual void start() = 0; // Must be called before using the proxy. + virtual void stop() = 0; // Must be called before deleting the proxy. + + // Forces 3D commands on all contexts to wait for all previous SwapBuffers to finish before executing in the GPU + // process. + virtual void forceSerializeOnSwapBuffers() = 0; + + // Maximum number of sub-region texture updates supported for each commit. + virtual size_t maxPartialTextureUpdates() const = 0; + + virtual void acquireLayerTextures() = 0; + + // Debug hooks +#ifndef NDEBUG + static bool isMainThread(); + static bool isImplThread(); + static bool isMainThreadBlocked(); + static void setMainThreadBlocked(bool); +#endif + + // Testing hooks + virtual void loseContext() = 0; + +#ifndef NDEBUG + static void setCurrentThreadIsImplThread(bool); +#endif + +protected: + CCProxy(); + friend class DebugScopedSetImplThread; + friend class DebugScopedSetMainThreadBlocked; +}; + +class DebugScopedSetMainThreadBlocked { +public: + DebugScopedSetMainThreadBlocked() + { +#if !ASSERT_DISABLED + ASSERT(!CCProxy::isMainThreadBlocked()); + CCProxy::setMainThreadBlocked(true); +#endif + } + ~DebugScopedSetMainThreadBlocked() + { +#if !ASSERT_DISABLED + ASSERT(CCProxy::isMainThreadBlocked()); + CCProxy::setMainThreadBlocked(false); +#endif + } +}; + +} + +#endif diff --git a/cc/CCQuadCuller.cpp b/cc/CCQuadCuller.cpp new file mode 100644 index 0000000..3197f11 --- /dev/null +++ b/cc/CCQuadCuller.cpp @@ -0,0 +1,95 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCQuadCuller.h" + +#include "CCDebugBorderDrawQuad.h" +#include "CCLayerImpl.h" +#include "CCOcclusionTracker.h" +#include "CCOverdrawMetrics.h" +#include "CCRenderPass.h" +#include "Region.h" +#include "SkColor.h" +#include <public/WebTransformationMatrix.h> + +using namespace std; + +namespace WebCore { + +static const int debugTileBorderWidth = 1; +static const int debugTileBorderAlpha = 120; +static const int debugTileBorderColorRed = 160; +static const int debugTileBorderColorGreen = 100; +static const int debugTileBorderColorBlue = 0; + +CCQuadCuller::CCQuadCuller(CCQuadList& quadList, CCSharedQuadStateList& sharedQuadStateList, CCLayerImpl* layer, const CCOcclusionTrackerImpl* occlusionTracker, bool showCullingWithDebugBorderQuads, bool forSurface) + : m_quadList(quadList) + , m_sharedQuadStateList(sharedQuadStateList) + , m_currentSharedQuadState(0) + , m_layer(layer) + , m_occlusionTracker(occlusionTracker) + , m_showCullingWithDebugBorderQuads(showCullingWithDebugBorderQuads) + , m_forSurface(forSurface) + , m_hasOcclusionFromOutsideTargetSurface(false) +{ +} + +CCSharedQuadState* CCQuadCuller::useSharedQuadState(PassOwnPtr<CCSharedQuadState> passSharedQuadState) +{ + OwnPtr<CCSharedQuadState> sharedQuadState(passSharedQuadState); + sharedQuadState->id = m_sharedQuadStateList.size(); + + // FIXME: If all quads are culled for the sharedQuadState, we can drop it from the list. + m_currentSharedQuadState = sharedQuadState.get(); + m_sharedQuadStateList.append(sharedQuadState.release()); + return m_currentSharedQuadState; +} + +static inline bool appendQuadInternal(PassOwnPtr<CCDrawQuad> passDrawQuad, const IntRect& culledRect, CCQuadList& quadList, const CCOcclusionTrackerImpl& occlusionTracker, bool createDebugBorderQuads) +{ + OwnPtr<CCDrawQuad> drawQuad(passDrawQuad); + bool keepQuad = !culledRect.isEmpty(); + if (keepQuad) + drawQuad->setQuadVisibleRect(culledRect); + + occlusionTracker.overdrawMetrics().didCullForDrawing(drawQuad->quadTransform(), drawQuad->quadRect(), culledRect); + occlusionTracker.overdrawMetrics().didDraw(drawQuad->quadTransform(), culledRect, drawQuad->opaqueRect()); + + if (keepQuad) { + if (createDebugBorderQuads && !drawQuad->isDebugQuad() && drawQuad->quadVisibleRect() != drawQuad->quadRect()) { + SkColor borderColor = SkColorSetARGB(debugTileBorderAlpha, debugTileBorderColorRed, debugTileBorderColorGreen, debugTileBorderColorBlue); + quadList.append(CCDebugBorderDrawQuad::create(drawQuad->sharedQuadState(), drawQuad->quadVisibleRect(), borderColor, debugTileBorderWidth)); + } + + // Release the quad after we're done using it. + quadList.append(drawQuad.release()); + } + return keepQuad; +} + +bool CCQuadCuller::append(PassOwnPtr<CCDrawQuad> passDrawQuad) +{ + ASSERT(passDrawQuad->sharedQuadState() == m_currentSharedQuadState); + ASSERT(passDrawQuad->sharedQuadStateId() == m_currentSharedQuadState->id); + ASSERT(!m_sharedQuadStateList.isEmpty()); + ASSERT(m_sharedQuadStateList.last().get() == m_currentSharedQuadState); + + IntRect culledRect; + bool hasOcclusionFromOutsideTargetSurface; + + if (m_forSurface) + culledRect = m_occlusionTracker->unoccludedContributingSurfaceContentRect(m_layer, false, passDrawQuad->quadRect(), &hasOcclusionFromOutsideTargetSurface); + else + culledRect = m_occlusionTracker->unoccludedContentRect(m_layer, passDrawQuad->quadRect(), &hasOcclusionFromOutsideTargetSurface); + m_hasOcclusionFromOutsideTargetSurface |= hasOcclusionFromOutsideTargetSurface; + + return appendQuadInternal(passDrawQuad, culledRect, m_quadList, *m_occlusionTracker, m_showCullingWithDebugBorderQuads); +} + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCQuadCuller.h b/cc/CCQuadCuller.h new file mode 100644 index 0000000..12adaaa --- /dev/null +++ b/cc/CCQuadCuller.h @@ -0,0 +1,40 @@ +// Copyright 2012 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. + +#ifndef CCQuadCuller_h +#define CCQuadCuller_h + +#include "CCQuadSink.h" +#include "CCRenderPass.h" + +namespace WebCore { +class CCLayerImpl; +class CCRenderSurface; +template<typename LayerType, typename SurfaceType> +class CCOcclusionTrackerBase; + +class CCQuadCuller : public CCQuadSink { +public: + CCQuadCuller(CCQuadList&, CCSharedQuadStateList&, CCLayerImpl*, const CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>*, bool showCullingWithDebugBorderQuads, bool forSurface); + virtual ~CCQuadCuller() { } + + // CCQuadSink implementation. + virtual CCSharedQuadState* useSharedQuadState(PassOwnPtr<CCSharedQuadState>) OVERRIDE; + virtual bool append(PassOwnPtr<CCDrawQuad>) OVERRIDE; + + bool hasOcclusionFromOutsideTargetSurface() { return m_hasOcclusionFromOutsideTargetSurface; } + +private: + CCQuadList& m_quadList; + CCSharedQuadStateList& m_sharedQuadStateList; + CCSharedQuadState* m_currentSharedQuadState; + CCLayerImpl* m_layer; + const CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>* m_occlusionTracker; + bool m_showCullingWithDebugBorderQuads; + bool m_forSurface; + bool m_hasOcclusionFromOutsideTargetSurface; +}; + +} +#endif // CCQuadCuller_h diff --git a/cc/CCQuadCullerTest.cpp b/cc/CCQuadCullerTest.cpp new file mode 100644 index 0000000..76dea20 --- /dev/null +++ b/cc/CCQuadCullerTest.cpp @@ -0,0 +1,470 @@ +// Copyright 2012 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 "CCQuadCuller.h" + +#include "CCLayerTilingData.h" +#include "CCMathUtil.h" +#include "CCOcclusionTracker.h" +#include "CCOverdrawMetrics.h" +#include "CCSingleThreadProxy.h" +#include "CCTileDrawQuad.h" +#include "CCTiledLayerImpl.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/WebTransformationMatrix.h> + +using namespace WebCore; +using WebKit::WebTransformationMatrix; + +namespace { + +class TestCCOcclusionTrackerImpl : public CCOcclusionTrackerImpl { +public: + TestCCOcclusionTrackerImpl(const IntRect& scissorRectInScreen, bool recordMetricsForFrame = true) + : CCOcclusionTrackerImpl(scissorRectInScreen, recordMetricsForFrame) + , m_scissorRectInScreen(scissorRectInScreen) + { + } + +protected: + virtual IntRect layerScissorRectInTargetSurface(const CCLayerImpl* layer) const { return m_scissorRectInScreen; } + +private: + IntRect m_scissorRectInScreen; +}; + +typedef CCLayerIterator<CCLayerImpl, Vector<CCLayerImpl*>, CCRenderSurface, CCLayerIteratorActions::FrontToBack> CCLayerIteratorType; + +static PassOwnPtr<CCTiledLayerImpl> makeLayer(CCTiledLayerImpl* parent, const WebTransformationMatrix& drawTransform, const IntRect& layerRect, float opacity, bool opaque, const IntRect& layerOpaqueRect, Vector<CCLayerImpl*>& surfaceLayerList) +{ + OwnPtr<CCTiledLayerImpl> layer = CCTiledLayerImpl::create(1); + OwnPtr<CCLayerTilingData> tiler = CCLayerTilingData::create(IntSize(100, 100), CCLayerTilingData::NoBorderTexels); + tiler->setBounds(layerRect.size()); + layer->setTilingData(*tiler); + layer->setSkipsDraw(false); + layer->setDrawTransform(drawTransform); + layer->setScreenSpaceTransform(drawTransform); + layer->setVisibleContentRect(layerRect); + layer->setDrawOpacity(opacity); + layer->setOpaque(opaque); + layer->setBounds(layerRect.size()); + layer->setContentBounds(layerRect.size()); + + CCResourceProvider::ResourceId resourceId = 1; + for (int i = 0; i < tiler->numTilesX(); ++i) + for (int j = 0; j < tiler->numTilesY(); ++j) { + IntRect tileOpaqueRect = opaque ? tiler->tileBounds(i, j) : intersection(tiler->tileBounds(i, j), layerOpaqueRect); + layer->pushTileProperties(i, j, resourceId++, tileOpaqueRect); + } + + IntRect rectInTarget = CCMathUtil::mapClippedRect(layer->drawTransform(), layer->visibleContentRect()); + if (!parent) { + layer->createRenderSurface(); + surfaceLayerList.append(layer.get()); + layer->renderSurface()->layerList().append(layer.get()); + } else { + layer->setRenderTarget(parent->renderTarget()); + parent->renderSurface()->layerList().append(layer.get()); + rectInTarget.unite(CCMathUtil::mapClippedRect(parent->drawTransform(), parent->visibleContentRect())); + } + layer->setDrawableContentRect(rectInTarget); + + return layer.release(); +} + +static void appendQuads(CCQuadList& quadList, CCSharedQuadStateList& sharedStateList, CCTiledLayerImpl* layer, CCLayerIteratorType& it, CCOcclusionTrackerImpl& occlusionTracker) +{ + occlusionTracker.enterLayer(it); + CCQuadCuller quadCuller(quadList, sharedStateList, layer, &occlusionTracker, false, false); + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); + occlusionTracker.leaveLayer(it); + ++it; +} + +#define DECLARE_AND_INITIALIZE_TEST_QUADS \ + DebugScopedSetImplThread impl; \ + CCQuadList quadList; \ + CCSharedQuadStateList sharedStateList; \ + Vector<CCLayerImpl*> renderSurfaceLayerList; \ + WebTransformationMatrix childTransform; \ + IntSize rootSize = IntSize(300, 300); \ + IntRect rootRect = IntRect(IntPoint(), rootSize); \ + IntSize childSize = IntSize(200, 200); \ + IntRect childRect = IntRect(IntPoint(), childSize); + +TEST(CCQuadCullerTest, verifyNoCulling) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), WebTransformationMatrix(), childRect, 1, false, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 13u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 90000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 40000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 0, 1); +} + +TEST(CCQuadCullerTest, verifyCullChildLinesUpTopLeft) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), WebTransformationMatrix(), childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 9u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 90000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 40000, 1); +} + +TEST(CCQuadCullerTest, verifyCullWhenChildOpacityNotOne) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), childTransform, childRect, 0.9f, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 13u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 90000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 40000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 0, 1); +} + +TEST(CCQuadCullerTest, verifyCullWhenChildOpaqueFlagFalse) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), childTransform, childRect, 1, false, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 13u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 90000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 40000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 0, 1); +} + +TEST(CCQuadCullerTest, verifyCullCenterTileOnly) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + childTransform.translate(50, 50); + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), childTransform, childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + ASSERT_EQ(quadList.size(), 12u); + + IntRect quadVisibleRect1 = quadList[5].get()->quadVisibleRect(); + EXPECT_EQ(quadVisibleRect1.height(), 50); + + IntRect quadVisibleRect3 = quadList[7].get()->quadVisibleRect(); + EXPECT_EQ(quadVisibleRect3.width(), 50); + + // Next index is 8, not 9, since centre quad culled. + IntRect quadVisibleRect4 = quadList[8].get()->quadVisibleRect(); + EXPECT_EQ(quadVisibleRect4.width(), 50); + EXPECT_EQ(quadVisibleRect4.x(), 250); + + IntRect quadVisibleRect6 = quadList[10].get()->quadVisibleRect(); + EXPECT_EQ(quadVisibleRect6.height(), 50); + EXPECT_EQ(quadVisibleRect6.y(), 250); + + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 100000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 30000, 1); +} + +TEST(CCQuadCullerTest, verifyCullCenterTileNonIntegralSize1) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + childTransform.translate(100, 100); + + // Make the root layer's quad have extent (99.1, 99.1) -> (200.9, 200.9) to make + // sure it doesn't get culled due to transform rounding. + WebTransformationMatrix rootTransform; + rootTransform.translate(99.1, 99.1); + rootTransform.scale(1.018); + + rootRect = childRect = IntRect(0, 0, 100, 100); + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, rootTransform, rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), childTransform, childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 2u); + + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 20363, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 0, 1); +} + +TEST(CCQuadCullerTest, verifyCullCenterTileNonIntegralSize2) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + // Make the child's quad slightly smaller than, and centred over, the root layer tile. + // Verify the child does not cause the quad below to be culled due to rounding. + childTransform.translate(100.1, 100.1); + childTransform.scale(0.982); + + WebTransformationMatrix rootTransform; + rootTransform.translate(100, 100); + + rootRect = childRect = IntRect(0, 0, 100, 100); + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, rootTransform, rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), childTransform, childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 2u); + + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 19643, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 0, 1); +} + +TEST(CCQuadCullerTest, verifyCullChildLinesUpBottomRight) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + childTransform.translate(100, 100); + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), childTransform, childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 9u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 90000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 40000, 1); +} + +TEST(CCQuadCullerTest, verifyCullSubRegion) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + childTransform.translate(50, 50); + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + IntRect childOpaqueRect(childRect.x() + childRect.width() / 4, childRect.y() + childRect.height() / 4, childRect.width() / 2, childRect.height() / 2); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), childTransform, childRect, 1, false, childOpaqueRect, renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 12u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 90000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 30000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 10000, 1); +} + +TEST(CCQuadCullerTest, verifyCullSubRegion2) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + childTransform.translate(50, 10); + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + IntRect childOpaqueRect(childRect.x() + childRect.width() / 4, childRect.y() + childRect.height() / 4, childRect.width() / 2, childRect.height() * 3 / 4); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), childTransform, childRect, 1, false, childOpaqueRect, renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 12u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 90000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 25000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 15000, 1); +} + +TEST(CCQuadCullerTest, verifyCullSubRegionCheckOvercull) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + childTransform.translate(50, 49); + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + IntRect childOpaqueRect(childRect.x() + childRect.width() / 4, childRect.y() + childRect.height() / 4, childRect.width() / 2, childRect.height() / 2); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), childTransform, childRect, 1, false, childOpaqueRect, renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 13u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 90000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 30000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 10000, 1); +} + +TEST(CCQuadCullerTest, verifyNonAxisAlignedQuadsDontOcclude) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + // Use a small rotation so as to not disturb the geometry significantly. + childTransform.rotate(1); + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), childTransform, childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 13u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 130000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 0, 1); +} + +// This test requires some explanation: here we are rotating the quads to be culled. +// The 2x2 tile child layer remains in the top-left corner, unrotated, but the 3x3 +// tile parent layer is rotated by 1 degree. Of the four tiles the child would +// normally occlude, three will move (slightly) out from under the child layer, and +// one moves further under the child. Only this last tile should be culled. +TEST(CCQuadCullerTest, verifyNonAxisAlignedQuadsSafelyCulled) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + // Use a small rotation so as to not disturb the geometry significantly. + WebTransformationMatrix parentTransform; + parentTransform.rotate(1); + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, parentTransform, rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), WebTransformationMatrix(), childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(-100, -100, 1000, 1000)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 12u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 100600, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 29400, 1); +} + +TEST(CCQuadCullerTest, verifyCullOutsideScissorOverTile) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), WebTransformationMatrix(), childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(200, 100, 100, 100)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 1u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 10000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 120000, 1); +} + +TEST(CCQuadCullerTest, verifyCullOutsideScissorOverCulledTile) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), WebTransformationMatrix(), childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(100, 100, 100, 100)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 1u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 10000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 120000, 1); +} + +TEST(CCQuadCullerTest, verifyCullOutsideScissorOverPartialTiles) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), WebTransformationMatrix(), childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(50, 50, 200, 200)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 9u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 40000, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 90000, 1); +} + +TEST(CCQuadCullerTest, verifyCullOutsideScissorOverNoTiles) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), WebTransformationMatrix(), childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(500, 500, 100, 100)); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 0u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 130000, 1); +} + +TEST(CCQuadCullerTest, verifyWithoutMetrics) +{ + DECLARE_AND_INITIALIZE_TEST_QUADS + + OwnPtr<CCTiledLayerImpl> rootLayer = makeLayer(0, WebTransformationMatrix(), rootRect, 1, true, IntRect(), renderSurfaceLayerList); + OwnPtr<CCTiledLayerImpl> childLayer = makeLayer(rootLayer.get(), WebTransformationMatrix(), childRect, 1, true, IntRect(), renderSurfaceLayerList); + TestCCOcclusionTrackerImpl occlusionTracker(IntRect(50, 50, 200, 200), false); + CCLayerIteratorType it = CCLayerIteratorType::begin(&renderSurfaceLayerList); + + appendQuads(quadList, sharedStateList, childLayer.get(), it, occlusionTracker); + appendQuads(quadList, sharedStateList, rootLayer.get(), it, occlusionTracker); + EXPECT_EQ(quadList.size(), 9u); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnOpaque(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsDrawnTranslucent(), 0, 1); + EXPECT_NEAR(occlusionTracker.overdrawMetrics().pixelsCulledForDrawing(), 0, 1); +} + + +} // namespace diff --git a/cc/CCQuadSink.h b/cc/CCQuadSink.h new file mode 100644 index 0000000..7f25fdd --- /dev/null +++ b/cc/CCQuadSink.h @@ -0,0 +1,29 @@ +// Copyright 2012 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. + +#ifndef CCQuadSink_h +#define CCQuadSink_h + +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCDrawQuad; + +struct CCSharedQuadState; + +class CCQuadSink { +public: + virtual ~CCQuadSink() { } + + // Call this to add a SharedQuadState before appending quads that refer to it. Returns a pointer + // to the given SharedQuadState for convenience, that can be set on the quads to append. + virtual CCSharedQuadState* useSharedQuadState(PassOwnPtr<CCSharedQuadState>) = 0; + + // Returns true if the quad is added to the list, and false if the quad is entirely culled. + virtual bool append(PassOwnPtr<CCDrawQuad> passDrawQuad) = 0; +}; + +} +#endif // CCQuadCuller_h diff --git a/cc/CCRenderPass.cpp b/cc/CCRenderPass.cpp new file mode 100644 index 0000000..2ab855f --- /dev/null +++ b/cc/CCRenderPass.cpp @@ -0,0 +1,92 @@ +// 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 "config.h" + +#include "CCRenderPass.h" + +#include "CCLayerImpl.h" +#include "CCMathUtil.h" +#include "CCOcclusionTracker.h" +#include "CCQuadCuller.h" +#include "CCSharedQuadState.h" +#include "CCSolidColorDrawQuad.h" + +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +PassOwnPtr<CCRenderPass> CCRenderPass::create(int id, IntRect outputRect, const WebKit::WebTransformationMatrix& transformToRootTarget) +{ + return adoptPtr(new CCRenderPass(id, outputRect, transformToRootTarget)); +} + +CCRenderPass::CCRenderPass(int id, IntRect outputRect, const WebKit::WebTransformationMatrix& transformToRootTarget) + : m_id(id) + , m_transformToRootTarget(transformToRootTarget) + , m_outputRect(outputRect) + , m_hasTransparentBackground(true) + , m_hasOcclusionFromOutsideTargetSurface(false) +{ + ASSERT(id > 0); +} + +void CCRenderPass::appendQuadsForLayer(CCLayerImpl* layer, CCOcclusionTrackerImpl* occlusionTracker, bool& hadMissingTiles) +{ + const bool forSurface = false; + CCQuadCuller quadCuller(m_quadList, m_sharedQuadStateList, layer, occlusionTracker, layer->hasDebugBorders(), forSurface); + + layer->appendQuads(quadCuller, hadMissingTiles); + + m_hasOcclusionFromOutsideTargetSurface |= quadCuller.hasOcclusionFromOutsideTargetSurface(); +} + +void CCRenderPass::appendQuadsForRenderSurfaceLayer(CCLayerImpl* layer, const CCRenderPass* contributingRenderPass, CCOcclusionTrackerImpl* occlusionTracker) +{ + const bool forSurface = true; + CCQuadCuller quadCuller(m_quadList, m_sharedQuadStateList, layer, occlusionTracker, layer->hasDebugBorders(), forSurface); + + bool isReplica = false; + layer->renderSurface()->appendQuads(quadCuller, isReplica, contributingRenderPass->id()); + + // Add replica after the surface so that it appears below the surface. + if (layer->hasReplica()) { + isReplica = true; + layer->renderSurface()->appendQuads(quadCuller, isReplica, contributingRenderPass->id()); + } + + m_hasOcclusionFromOutsideTargetSurface |= quadCuller.hasOcclusionFromOutsideTargetSurface(); +} + +void CCRenderPass::appendQuadsToFillScreen(CCLayerImpl* rootLayer, SkColor screenBackgroundColor, const CCOcclusionTrackerImpl& occlusionTracker) +{ + if (!rootLayer || !screenBackgroundColor) + return; + + Region fillRegion = occlusionTracker.computeVisibleRegionInScreen(); + if (fillRegion.isEmpty()) + return; + + bool forSurface = false; + CCQuadCuller quadCuller(m_quadList, m_sharedQuadStateList, rootLayer, &occlusionTracker, rootLayer->hasDebugBorders(), forSurface); + + // Manually create the quad state for the gutter quads, as the root layer + // doesn't have any bounds and so can't generate this itself. + // FIXME: Make the gutter quads generated by the solid color layer (make it smarter about generating quads to fill unoccluded areas). + IntRect rootTargetRect = rootLayer->renderSurface()->contentRect(); + float opacity = 1; + bool opaque = true; + CCSharedQuadState* sharedQuadState = quadCuller.useSharedQuadState(CCSharedQuadState::create(rootLayer->drawTransform(), rootTargetRect, rootTargetRect, opacity, opaque)); + ASSERT(rootLayer->screenSpaceTransform().isInvertible()); + WebTransformationMatrix transformToLayerSpace = rootLayer->screenSpaceTransform().inverse(); + Vector<IntRect> fillRects = fillRegion.rects(); + for (size_t i = 0; i < fillRects.size(); ++i) { + // The root layer transform is composed of translations and scales only, no perspective, so mapping is sufficient. + IntRect layerRect = CCMathUtil::mapClippedRect(transformToLayerSpace, fillRects[i]); + // Skip the quad culler and just append the quads directly to avoid occlusion checks. + m_quadList.append(CCSolidColorDrawQuad::create(sharedQuadState, layerRect, screenBackgroundColor)); + } +} + +} diff --git a/cc/CCRenderPass.h b/cc/CCRenderPass.h new file mode 100644 index 0000000..b2fa189 --- /dev/null +++ b/cc/CCRenderPass.h @@ -0,0 +1,91 @@ +// 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. + +#ifndef CCRenderPass_h +#define CCRenderPass_h + +#include "CCDrawQuad.h" +#include "CCOcclusionTracker.h" +#include "CCSharedQuadState.h" +#include "SkColor.h" +#include <public/WebFilterOperations.h> +#include <public/WebTransformationMatrix.h> +#include <wtf/HashMap.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class CCLayerImpl; +class CCRenderSurface; + +// A list of CCDrawQuad objects, sorted internally in front-to-back order. +class CCQuadList : public Vector<OwnPtr<CCDrawQuad> > { +public: + typedef reverse_iterator backToFrontIterator; + typedef const_reverse_iterator constBackToFrontIterator; + + inline backToFrontIterator backToFrontBegin() { return rbegin(); } + inline backToFrontIterator backToFrontEnd() { return rend(); } + inline constBackToFrontIterator backToFrontBegin() const { return rbegin(); } + inline constBackToFrontIterator backToFrontEnd() const { return rend(); } +}; + +typedef Vector<OwnPtr<CCSharedQuadState> > CCSharedQuadStateList; + +class CCRenderPass { + WTF_MAKE_NONCOPYABLE(CCRenderPass); +public: + static PassOwnPtr<CCRenderPass> create(int id, IntRect outputRect, const WebKit::WebTransformationMatrix& transformToRootTarget); + + void appendQuadsForLayer(CCLayerImpl*, CCOcclusionTrackerImpl*, bool& hadMissingTiles); + void appendQuadsForRenderSurfaceLayer(CCLayerImpl*, const CCRenderPass* contributingRenderPass, CCOcclusionTrackerImpl*); + void appendQuadsToFillScreen(CCLayerImpl* rootLayer, SkColor screenBackgroundColor, const CCOcclusionTrackerImpl&); + + const CCQuadList& quadList() const { return m_quadList; } + + int id() const { return m_id; } + + // FIXME: Modify this transform when merging the RenderPass into a parent compositor. + // Transforms from quad's original content space to the root target's content space. + const WebKit::WebTransformationMatrix& transformToRootTarget() const { return m_transformToRootTarget; } + + // This denotes the bounds in physical pixels of the output generated by this RenderPass. + const IntRect& outputRect() const { return m_outputRect; } + + FloatRect damageRect() const { return m_damageRect; } + void setDamageRect(FloatRect rect) { m_damageRect = rect; } + + const WebKit::WebFilterOperations& filters() const { return m_filters; } + void setFilters(const WebKit::WebFilterOperations& filters) { m_filters = filters; } + + const WebKit::WebFilterOperations& backgroundFilters() const { return m_backgroundFilters; } + void setBackgroundFilters(const WebKit::WebFilterOperations& filters) { m_backgroundFilters = filters; } + + bool hasTransparentBackground() const { return m_hasTransparentBackground; } + void setHasTransparentBackground(bool transparent) { m_hasTransparentBackground = transparent; } + + bool hasOcclusionFromOutsideTargetSurface() const { return m_hasOcclusionFromOutsideTargetSurface; } + void setHasOcclusionFromOutsideTargetSurface(bool hasOcclusionFromOutsideTargetSurface) { m_hasOcclusionFromOutsideTargetSurface = hasOcclusionFromOutsideTargetSurface; } +protected: + CCRenderPass(int id, IntRect outputRect, const WebKit::WebTransformationMatrix& transformToRootTarget); + + int m_id; + CCQuadList m_quadList; + CCSharedQuadStateList m_sharedQuadStateList; + WebKit::WebTransformationMatrix m_transformToRootTarget; + IntRect m_outputRect; + FloatRect m_damageRect; + bool m_hasTransparentBackground; + bool m_hasOcclusionFromOutsideTargetSurface; + WebKit::WebFilterOperations m_filters; + WebKit::WebFilterOperations m_backgroundFilters; +}; + +typedef Vector<CCRenderPass*> CCRenderPassList; +typedef HashMap<int, OwnPtr<CCRenderPass> > CCRenderPassIdHashMap; + +} + +#endif diff --git a/cc/CCRenderPassDrawQuad.cpp b/cc/CCRenderPassDrawQuad.cpp new file mode 100644 index 0000000..96089c4 --- /dev/null +++ b/cc/CCRenderPassDrawQuad.cpp @@ -0,0 +1,36 @@ +// 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 "config.h" + +#include "CCRenderPassDrawQuad.h" + +namespace WebCore { + +PassOwnPtr<CCRenderPassDrawQuad> CCRenderPassDrawQuad::create(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, int renderPassId, bool isReplica, const CCResourceProvider::ResourceId maskResourceId, const IntRect& contentsChangedSinceLastFrame, float maskTexCoordScaleX, float maskTexCoordScaleY, float maskTexCoordOffsetX, float maskTexCoordOffsetY) +{ + return adoptPtr(new CCRenderPassDrawQuad(sharedQuadState, quadRect, renderPassId, isReplica, maskResourceId, contentsChangedSinceLastFrame, maskTexCoordScaleX, maskTexCoordScaleY, maskTexCoordOffsetX, maskTexCoordOffsetY)); +} + +CCRenderPassDrawQuad::CCRenderPassDrawQuad(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, int renderPassId, bool isReplica, CCResourceProvider::ResourceId maskResourceId, const IntRect& contentsChangedSinceLastFrame, float maskTexCoordScaleX, float maskTexCoordScaleY, float maskTexCoordOffsetX, float maskTexCoordOffsetY) + : CCDrawQuad(sharedQuadState, CCDrawQuad::RenderPass, quadRect) + , m_renderPassId(renderPassId) + , m_isReplica(isReplica) + , m_maskResourceId(maskResourceId) + , m_contentsChangedSinceLastFrame(contentsChangedSinceLastFrame) + , m_maskTexCoordScaleX(maskTexCoordScaleX) + , m_maskTexCoordScaleY(maskTexCoordScaleY) + , m_maskTexCoordOffsetX(maskTexCoordOffsetX) + , m_maskTexCoordOffsetY(maskTexCoordOffsetY) +{ + ASSERT(m_renderPassId > 0); +} + +const CCRenderPassDrawQuad* CCRenderPassDrawQuad::materialCast(const CCDrawQuad* quad) +{ + ASSERT(quad->material() == CCDrawQuad::RenderPass); + return static_cast<const CCRenderPassDrawQuad*>(quad); +} + +} diff --git a/cc/CCRenderPassDrawQuad.h b/cc/CCRenderPassDrawQuad.h new file mode 100644 index 0000000..504e6e1 --- /dev/null +++ b/cc/CCRenderPassDrawQuad.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef CCRenderPassDrawQuad_h +#define CCRenderPassDrawQuad_h + +#include "CCDrawQuad.h" +#include "CCResourceProvider.h" +#include "IntRect.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCRenderPass; + +class CCRenderPassDrawQuad : public CCDrawQuad { + WTF_MAKE_NONCOPYABLE(CCRenderPassDrawQuad); +public: + static PassOwnPtr<CCRenderPassDrawQuad> create(const CCSharedQuadState*, const IntRect&, int renderPassId, bool isReplica, CCResourceProvider::ResourceId maskResourceId, const IntRect& contentsChangedSinceLastFrame, float maskTexCoordScaleX, float maskTexCoordScaleY, float maskTexCoordOffsetX, float maskTexCoordOffsetY); + + int renderPassId() const { return m_renderPassId; } + bool isReplica() const { return m_isReplica; } + CCResourceProvider::ResourceId maskResourceId() const { return m_maskResourceId; } + const IntRect& contentsChangedSinceLastFrame() const { return m_contentsChangedSinceLastFrame; } + + static const CCRenderPassDrawQuad* materialCast(const CCDrawQuad*); + float maskTexCoordScaleX() const { return m_maskTexCoordScaleX; } + float maskTexCoordScaleY() const { return m_maskTexCoordScaleY; } + float maskTexCoordOffsetX() const { return m_maskTexCoordOffsetX; } + float maskTexCoordOffsetY() const { return m_maskTexCoordOffsetY; } + +private: + CCRenderPassDrawQuad(const CCSharedQuadState*, const IntRect&, int renderPassId, bool isReplica, CCResourceProvider::ResourceId maskResourceId, const IntRect& contentsChangedSinceLastFrame, float maskTexCoordScaleX, float maskTexCoordScaleY, float maskTexCoordOffsetX, float maskTexCoordOffsetY); + + int m_renderPassId; + bool m_isReplica; + CCResourceProvider::ResourceId m_maskResourceId; + IntRect m_contentsChangedSinceLastFrame; + float m_maskTexCoordScaleX; + float m_maskTexCoordScaleY; + float m_maskTexCoordOffsetX; + float m_maskTexCoordOffsetY; +}; + +} + +#endif diff --git a/cc/CCRenderSurface.cpp b/cc/CCRenderSurface.cpp new file mode 100644 index 0000000..8e26b73 --- /dev/null +++ b/cc/CCRenderSurface.cpp @@ -0,0 +1,213 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCRenderSurface.h" + +#include "CCDamageTracker.h" +#include "CCDebugBorderDrawQuad.h" +#include "CCLayerImpl.h" +#include "CCMathUtil.h" +#include "CCQuadSink.h" +#include "CCRenderPassDrawQuad.h" +#include "CCSharedQuadState.h" +#include "TextStream.h" +#include <public/WebTransformationMatrix.h> +#include <wtf/text/CString.h> + +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +static const int debugSurfaceBorderWidth = 2; +static const int debugSurfaceBorderAlpha = 100; +static const int debugSurfaceBorderColorRed = 0; +static const int debugSurfaceBorderColorGreen = 0; +static const int debugSurfaceBorderColorBlue = 255; +static const int debugReplicaBorderColorRed = 160; +static const int debugReplicaBorderColorGreen = 0; +static const int debugReplicaBorderColorBlue = 255; + +CCRenderSurface::CCRenderSurface(CCLayerImpl* owningLayer) + : m_owningLayer(owningLayer) + , m_surfacePropertyChanged(false) + , m_drawOpacity(1) + , m_drawOpacityIsAnimating(false) + , m_targetSurfaceTransformsAreAnimating(false) + , m_screenSpaceTransformsAreAnimating(false) + , m_nearestAncestorThatMovesPixels(0) + , m_targetRenderSurfaceLayerIndexHistory(0) + , m_currentLayerIndexHistory(0) +{ + m_damageTracker = CCDamageTracker::create(); +} + +CCRenderSurface::~CCRenderSurface() +{ +} + +FloatRect CCRenderSurface::drawableContentRect() const +{ + FloatRect drawableContentRect = CCMathUtil::mapClippedRect(m_drawTransform, m_contentRect); + if (m_owningLayer->hasReplica()) + drawableContentRect.unite(CCMathUtil::mapClippedRect(m_replicaDrawTransform, m_contentRect)); + + return drawableContentRect; +} + +String CCRenderSurface::name() const +{ + return String::format("RenderSurface(id=%i,owner=%s)", m_owningLayer->id(), m_owningLayer->debugName().utf8().data()); +} + +static void writeIndent(TextStream& ts, int indent) +{ + for (int i = 0; i != indent; ++i) + ts << " "; +} + +void CCRenderSurface::dumpSurface(TextStream& ts, int indent) const +{ + writeIndent(ts, indent); + ts << name() << "\n"; + + writeIndent(ts, indent+1); + ts << "contentRect: (" << m_contentRect.x() << ", " << m_contentRect.y() << ", " << m_contentRect.width() << ", " << m_contentRect.height() << "\n"; + + writeIndent(ts, indent+1); + ts << "drawTransform: "; + ts << m_drawTransform.m11() << ", " << m_drawTransform.m12() << ", " << m_drawTransform.m13() << ", " << m_drawTransform.m14() << " // "; + ts << m_drawTransform.m21() << ", " << m_drawTransform.m22() << ", " << m_drawTransform.m23() << ", " << m_drawTransform.m24() << " // "; + ts << m_drawTransform.m31() << ", " << m_drawTransform.m32() << ", " << m_drawTransform.m33() << ", " << m_drawTransform.m34() << " // "; + ts << m_drawTransform.m41() << ", " << m_drawTransform.m42() << ", " << m_drawTransform.m43() << ", " << m_drawTransform.m44() << "\n"; + + writeIndent(ts, indent+1); + ts << "damageRect is pos(" << m_damageTracker->currentDamageRect().x() << "," << m_damageTracker->currentDamageRect().y() << "), "; + ts << "size(" << m_damageTracker->currentDamageRect().width() << "," << m_damageTracker->currentDamageRect().height() << ")\n"; +} + +int CCRenderSurface::owningLayerId() const +{ + return m_owningLayer ? m_owningLayer->id() : 0; +} + + +void CCRenderSurface::setClipRect(const IntRect& clipRect) +{ + if (m_clipRect == clipRect) + return; + + m_surfacePropertyChanged = true; + m_clipRect = clipRect; +} + +bool CCRenderSurface::contentsChanged() const +{ + return !m_damageTracker->currentDamageRect().isEmpty(); +} + +void CCRenderSurface::setContentRect(const IntRect& contentRect) +{ + if (m_contentRect == contentRect) + return; + + m_surfacePropertyChanged = true; + m_contentRect = contentRect; +} + +bool CCRenderSurface::surfacePropertyChanged() const +{ + // Surface property changes are tracked as follows: + // + // - m_surfacePropertyChanged is flagged when the clipRect or contentRect change. As + // of now, these are the only two properties that can be affected by descendant layers. + // + // - all other property changes come from the owning layer (or some ancestor layer + // that propagates its change to the owning layer). + // + ASSERT(m_owningLayer); + return m_surfacePropertyChanged || m_owningLayer->layerPropertyChanged(); +} + +bool CCRenderSurface::surfacePropertyChangedOnlyFromDescendant() const +{ + return m_surfacePropertyChanged && !m_owningLayer->layerPropertyChanged(); +} + +static inline IntRect computeClippedRectInTarget(const CCLayerImpl* owningLayer) +{ + ASSERT(owningLayer->parent()); + + const CCLayerImpl* renderTarget = owningLayer->parent()->renderTarget(); + const CCRenderSurface* self = owningLayer->renderSurface(); + + IntRect clippedRectInTarget = self->clipRect(); + if (owningLayer->backgroundFilters().hasFilterThatMovesPixels()) { + // If the layer has background filters that move pixels, we cannot scissor as tightly. + // FIXME: this should be able to be a tighter scissor, perhaps expanded by the filter outsets? + clippedRectInTarget = renderTarget->renderSurface()->contentRect(); + } else if (clippedRectInTarget.isEmpty()) { + // For surfaces, empty clipRect means that the surface does not clip anything. + clippedRectInTarget = enclosingIntRect(intersection(renderTarget->renderSurface()->contentRect(), self->drawableContentRect())); + } else + clippedRectInTarget.intersect(enclosingIntRect(self->drawableContentRect())); + return clippedRectInTarget; +} + +void CCRenderSurface::appendQuads(CCQuadSink& quadSink, bool forReplica, int renderPassId) +{ + ASSERT(!forReplica || m_owningLayer->hasReplica()); + + IntRect clippedRectInTarget = computeClippedRectInTarget(m_owningLayer); + bool isOpaque = false; + const WebTransformationMatrix& drawTransform = forReplica ? m_replicaDrawTransform : m_drawTransform; + CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(CCSharedQuadState::create(drawTransform, m_contentRect, clippedRectInTarget, m_drawOpacity, isOpaque)); + + if (m_owningLayer->hasDebugBorders()) { + int red = forReplica ? debugReplicaBorderColorRed : debugSurfaceBorderColorRed; + int green = forReplica ? debugReplicaBorderColorGreen : debugSurfaceBorderColorGreen; + int blue = forReplica ? debugReplicaBorderColorBlue : debugSurfaceBorderColorBlue; + SkColor color = SkColorSetARGB(debugSurfaceBorderAlpha, red, green, blue); + quadSink.append(CCDebugBorderDrawQuad::create(sharedQuadState, contentRect(), color, debugSurfaceBorderWidth)); + } + + // FIXME: By using the same RenderSurface for both the content and its reflection, + // it's currently not possible to apply a separate mask to the reflection layer + // or correctly handle opacity in reflections (opacity must be applied after drawing + // both the layer and its reflection). The solution is to introduce yet another RenderSurface + // to draw the layer and its reflection in. For now we only apply a separate reflection + // mask if the contents don't have a mask of their own. + CCLayerImpl* maskLayer = m_owningLayer->maskLayer(); + if (maskLayer && (!maskLayer->drawsContent() || maskLayer->bounds().isEmpty())) + maskLayer = 0; + + if (!maskLayer && forReplica) { + maskLayer = m_owningLayer->replicaLayer()->maskLayer(); + if (maskLayer && (!maskLayer->drawsContent() || maskLayer->bounds().isEmpty())) + maskLayer = 0; + } + + float maskTexCoordScaleX = 1; + float maskTexCoordScaleY = 1; + float maskTexCoordOffsetX = 1; + float maskTexCoordOffsetY = 1; + if (maskLayer) { + maskTexCoordScaleX = static_cast<float>(contentRect().width()) / maskLayer->contentBounds().width(); + maskTexCoordScaleY = static_cast<float>(contentRect().height()) / maskLayer->contentBounds().height(); + maskTexCoordOffsetX = static_cast<float>(contentRect().x()) / contentRect().width() * maskTexCoordScaleX; + maskTexCoordOffsetY = static_cast<float>(contentRect().y()) / contentRect().height() * maskTexCoordScaleY; + } + + CCResourceProvider::ResourceId maskResourceId = maskLayer ? maskLayer->contentsResourceId() : 0; + IntRect contentsChangedSinceLastFrame = contentsChanged() ? m_contentRect : IntRect(); + + quadSink.append(CCRenderPassDrawQuad::create(sharedQuadState, contentRect(), renderPassId, forReplica, maskResourceId, contentsChangedSinceLastFrame, + maskTexCoordScaleX, maskTexCoordScaleY, maskTexCoordOffsetX, maskTexCoordOffsetY)); +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCRenderSurface.h b/cc/CCRenderSurface.h new file mode 100644 index 0000000..df62d34 --- /dev/null +++ b/cc/CCRenderSurface.h @@ -0,0 +1,124 @@ +// 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. + + +#ifndef CCRenderSurface_h +#define CCRenderSurface_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCSharedQuadState.h" +#include "FloatRect.h" +#include "IntRect.h" +#include <public/WebTransformationMatrix.h> +#include <wtf/Noncopyable.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class CCDamageTracker; +class CCQuadSink; +class CCRenderPass; +class CCLayerImpl; +class TextStream; + +class CCRenderSurface { + WTF_MAKE_NONCOPYABLE(CCRenderSurface); +public: + explicit CCRenderSurface(CCLayerImpl*); + virtual ~CCRenderSurface(); + + String name() const; + void dumpSurface(TextStream&, int indent) const; + + FloatPoint contentRectCenter() const { return FloatRect(m_contentRect).center(); } + + // Returns the rect that encloses the RenderSurface including any reflection. + FloatRect drawableContentRect() const; + + float drawOpacity() const { return m_drawOpacity; } + void setDrawOpacity(float opacity) { m_drawOpacity = opacity; } + + void setNearestAncestorThatMovesPixels(CCRenderSurface* surface) { m_nearestAncestorThatMovesPixels = surface; } + const CCRenderSurface* nearestAncestorThatMovesPixels() const { return m_nearestAncestorThatMovesPixels; } + + bool drawOpacityIsAnimating() const { return m_drawOpacityIsAnimating; } + void setDrawOpacityIsAnimating(bool drawOpacityIsAnimating) { m_drawOpacityIsAnimating = drawOpacityIsAnimating; } + + void setDrawTransform(const WebKit::WebTransformationMatrix& drawTransform) { m_drawTransform = drawTransform; } + const WebKit::WebTransformationMatrix& drawTransform() const { return m_drawTransform; } + + void setScreenSpaceTransform(const WebKit::WebTransformationMatrix& screenSpaceTransform) { m_screenSpaceTransform = screenSpaceTransform; } + const WebKit::WebTransformationMatrix& screenSpaceTransform() const { return m_screenSpaceTransform; } + + void setReplicaDrawTransform(const WebKit::WebTransformationMatrix& replicaDrawTransform) { m_replicaDrawTransform = replicaDrawTransform; } + const WebKit::WebTransformationMatrix& replicaDrawTransform() const { return m_replicaDrawTransform; } + + void setReplicaScreenSpaceTransform(const WebKit::WebTransformationMatrix& replicaScreenSpaceTransform) { m_replicaScreenSpaceTransform = replicaScreenSpaceTransform; } + const WebKit::WebTransformationMatrix& replicaScreenSpaceTransform() const { return m_replicaScreenSpaceTransform; } + + bool targetSurfaceTransformsAreAnimating() const { return m_targetSurfaceTransformsAreAnimating; } + void setTargetSurfaceTransformsAreAnimating(bool animating) { m_targetSurfaceTransformsAreAnimating = animating; } + bool screenSpaceTransformsAreAnimating() const { return m_screenSpaceTransformsAreAnimating; } + void setScreenSpaceTransformsAreAnimating(bool animating) { m_screenSpaceTransformsAreAnimating = animating; } + + void setClipRect(const IntRect&); + const IntRect& clipRect() const { return m_clipRect; } + + bool contentsChanged() const; + + void setContentRect(const IntRect&); + const IntRect& contentRect() const { return m_contentRect; } + + void clearLayerList() { m_layerList.clear(); } + Vector<CCLayerImpl*>& layerList() { return m_layerList; } + + int owningLayerId() const; + + void resetPropertyChangedFlag() { m_surfacePropertyChanged = false; } + bool surfacePropertyChanged() const; + bool surfacePropertyChangedOnlyFromDescendant() const; + + CCDamageTracker* damageTracker() const { return m_damageTracker.get(); } + + void appendQuads(CCQuadSink&, bool forReplica, int renderPassId); + +private: + CCLayerImpl* m_owningLayer; + + // Uses this surface's space. + IntRect m_contentRect; + bool m_surfacePropertyChanged; + + float m_drawOpacity; + bool m_drawOpacityIsAnimating; + WebKit::WebTransformationMatrix m_drawTransform; + WebKit::WebTransformationMatrix m_screenSpaceTransform; + WebKit::WebTransformationMatrix m_replicaDrawTransform; + WebKit::WebTransformationMatrix m_replicaScreenSpaceTransform; + bool m_targetSurfaceTransformsAreAnimating; + bool m_screenSpaceTransformsAreAnimating; + + // Uses the space of the surface's target surface. + IntRect m_clipRect; + + Vector<CCLayerImpl*> m_layerList; + + // The nearest ancestor target surface that will contain the contents of this surface, and that is going + // to move pixels within the surface (such as with a blur). This can point to itself. + CCRenderSurface* m_nearestAncestorThatMovesPixels; + + OwnPtr<CCDamageTracker> m_damageTracker; + + // For CCLayerIteratorActions + int m_targetRenderSurfaceLayerIndexHistory; + int m_currentLayerIndexHistory; + + friend struct CCLayerIteratorActions; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/CCRenderSurfaceFilters.cpp b/cc/CCRenderSurfaceFilters.cpp new file mode 100644 index 0000000..3ea246e --- /dev/null +++ b/cc/CCRenderSurfaceFilters.cpp @@ -0,0 +1,443 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCRenderSurfaceFilters.h" + +#include "FloatSize.h" +#include "SkBlurImageFilter.h" +#include "SkCanvas.h" +#include "SkColorMatrixFilter.h" +#include "SkGpuDevice.h" +#include "SkGrTexturePixelRef.h" +#include "SkMagnifierImageFilter.h" +#include <public/WebFilterOperation.h> +#include <public/WebFilterOperations.h> +#include <public/WebGraphicsContext3D.h> +#include <wtf/MathExtras.h> + +namespace { + +void getBrightnessMatrix(float amount, SkScalar matrix[20]) +{ + memset(matrix, 0, 20 * sizeof(SkScalar)); + // Old implementation, a la the draft spec, a straight-up scale, + // representing <feFunc[R|G|B] type="linear" slope="[amount]"> + // (See http://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html#brightnessEquivalent) + // matrix[0] = matrix[6] = matrix[12] = amount; + // matrix[18] = 1; + // New implementation, a translation in color space, representing + // <feFunc[R|G|B] type="linear" intercept="[amount]"/> + // (See https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647) + matrix[0] = matrix[6] = matrix[12] = matrix[18] = 1; + matrix[4] = matrix[9] = matrix[14] = amount * 255; +} + +void getContrastMatrix(float amount, SkScalar matrix[20]) +{ + memset(matrix, 0, 20 * sizeof(SkScalar)); + matrix[0] = matrix[6] = matrix[12] = amount; + matrix[4] = matrix[9] = matrix[14] = (-0.5f * amount + 0.5f) * 255; + matrix[18] = 1; +} + +void getSaturateMatrix(float amount, SkScalar matrix[20]) +{ + // Note, these values are computed to ensure matrixNeedsClamping is false + // for amount in [0..1] + matrix[0] = 0.213f + 0.787f * amount; + matrix[1] = 0.715f - 0.715f * amount; + matrix[2] = 1.f - (matrix[0] + matrix[1]); + matrix[3] = matrix[4] = 0; + matrix[5] = 0.213f - 0.213f * amount; + matrix[6] = 0.715f + 0.285f * amount; + matrix[7] = 1.f - (matrix[5] + matrix[6]); + matrix[8] = matrix[9] = 0; + matrix[10] = 0.213f - 0.213f * amount; + matrix[11] = 0.715f - 0.715f * amount; + matrix[12] = 1.f - (matrix[10] + matrix[11]); + matrix[13] = matrix[14] = 0; + matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0; + matrix[18] = 1; +} + +void getHueRotateMatrix(float hue, SkScalar matrix[20]) +{ + float cosHue = cosf(hue * piFloat / 180); + float sinHue = sinf(hue * piFloat / 180); + matrix[0] = 0.213f + cosHue * 0.787f - sinHue * 0.213f; + matrix[1] = 0.715f - cosHue * 0.715f - sinHue * 0.715f; + matrix[2] = 0.072f - cosHue * 0.072f + sinHue * 0.928f; + matrix[3] = matrix[4] = 0; + matrix[5] = 0.213f - cosHue * 0.213f + sinHue * 0.143f; + matrix[6] = 0.715f + cosHue * 0.285f + sinHue * 0.140f; + matrix[7] = 0.072f - cosHue * 0.072f - sinHue * 0.283f; + matrix[8] = matrix[9] = 0; + matrix[10] = 0.213f - cosHue * 0.213f - sinHue * 0.787f; + matrix[11] = 0.715f - cosHue * 0.715f + sinHue * 0.715f; + matrix[12] = 0.072f + cosHue * 0.928f + sinHue * 0.072f; + matrix[13] = matrix[14] = 0; + matrix[15] = matrix[16] = matrix[17] = 0; + matrix[18] = 1; + matrix[19] = 0; +} + +void getInvertMatrix(float amount, SkScalar matrix[20]) +{ + memset(matrix, 0, 20 * sizeof(SkScalar)); + matrix[0] = matrix[6] = matrix[12] = 1 - 2 * amount; + matrix[4] = matrix[9] = matrix[14] = amount * 255; + matrix[18] = 1; +} + +void getOpacityMatrix(float amount, SkScalar matrix[20]) +{ + memset(matrix, 0, 20 * sizeof(SkScalar)); + matrix[0] = matrix[6] = matrix[12] = 1; + matrix[18] = amount; +} + +void getGrayscaleMatrix(float amount, SkScalar matrix[20]) +{ + // Note, these values are computed to ensure matrixNeedsClamping is false + // for amount in [0..1] + matrix[0] = 0.2126f + 0.7874f * amount; + matrix[1] = 0.7152f - 0.7152f * amount; + matrix[2] = 1.f - (matrix[0] + matrix[1]); + matrix[3] = matrix[4] = 0; + + matrix[5] = 0.2126f - 0.2126f * amount; + matrix[6] = 0.7152f + 0.2848f * amount; + matrix[7] = 1.f - (matrix[5] + matrix[6]); + matrix[8] = matrix[9] = 0; + + matrix[10] = 0.2126f - 0.2126f * amount; + matrix[11] = 0.7152f - 0.7152f * amount; + matrix[12] = 1.f - (matrix[10] + matrix[11]); + matrix[13] = matrix[14] = 0; + + matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0; + matrix[18] = 1; +} + +void getSepiaMatrix(float amount, SkScalar matrix[20]) +{ + matrix[0] = 0.393f + 0.607f * amount; + matrix[1] = 0.769f - 0.769f * amount; + matrix[2] = 0.189f - 0.189f * amount; + matrix[3] = matrix[4] = 0; + + matrix[5] = 0.349f - 0.349f * amount; + matrix[6] = 0.686f + 0.314f * amount; + matrix[7] = 0.168f - 0.168f * amount; + matrix[8] = matrix[9] = 0; + + matrix[10] = 0.272f - 0.272f * amount; + matrix[11] = 0.534f - 0.534f * amount; + matrix[12] = 0.131f + 0.869f * amount; + matrix[13] = matrix[14] = 0; + + matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0; + matrix[18] = 1; +} + +// The 5x4 matrix is really a "compressed" version of a 5x5 matrix that'd have +// (0 0 0 0 1) as a last row, and that would be applied to a 5-vector extended +// from the 4-vector color with a 1. +void multColorMatrix(SkScalar a[20], SkScalar b[20], SkScalar out[20]) +{ + for (int j = 0; j < 4; ++j) { + for (int i = 0; i < 5; ++i) { + out[i+j*5] = i == 4 ? a[4+j*5] : 0; + for (int k = 0; k < 4; ++k) + out[i+j*5] += a[k+j*5] * b[i+k*5]; + } + } +} + +// To detect if we need to apply clamping after applying a matrix, we check if +// any output component might go outside of [0, 255] for any combination of +// input components in [0..255]. +// Each output component is an affine transformation of the input component, so +// the minimum and maximum values are for any combination of minimum or maximum +// values of input components (i.e. 0 or 255). +// E.g. if R' = x*R + y*G + z*B + w*A + t +// Then the maximum value will be for R=255 if x>0 or R=0 if x<0, and the +// minimum value will be for R=0 if x>0 or R=255 if x<0. +// Same goes for all components. +bool componentNeedsClamping(SkScalar row[5]) +{ + SkScalar maxValue = row[4] / 255; + SkScalar minValue = row[4] / 255; + for (int i = 0; i < 4; ++i) { + if (row[i] > 0) + maxValue += row[i]; + else + minValue += row[i]; + } + return (maxValue > 1) || (minValue < 0); +} + +bool matrixNeedsClamping(SkScalar matrix[20]) +{ + return componentNeedsClamping(matrix) + || componentNeedsClamping(matrix+5) + || componentNeedsClamping(matrix+10) + || componentNeedsClamping(matrix+15); +} + +bool getColorMatrix(const WebKit::WebFilterOperation& op, SkScalar matrix[20]) +{ + switch (op.type()) { + case WebKit::WebFilterOperation::FilterTypeBrightness: { + getBrightnessMatrix(op.amount(), matrix); + return true; + } + case WebKit::WebFilterOperation::FilterTypeContrast: { + getContrastMatrix(op.amount(), matrix); + return true; + } + case WebKit::WebFilterOperation::FilterTypeGrayscale: { + getGrayscaleMatrix(1 - op.amount(), matrix); + return true; + } + case WebKit::WebFilterOperation::FilterTypeSepia: { + getSepiaMatrix(1 - op.amount(), matrix); + return true; + } + case WebKit::WebFilterOperation::FilterTypeSaturate: { + getSaturateMatrix(op.amount(), matrix); + return true; + } + case WebKit::WebFilterOperation::FilterTypeHueRotate: { + getHueRotateMatrix(op.amount(), matrix); + return true; + } + case WebKit::WebFilterOperation::FilterTypeInvert: { + getInvertMatrix(op.amount(), matrix); + return true; + } + case WebKit::WebFilterOperation::FilterTypeOpacity: { + getOpacityMatrix(op.amount(), matrix); + return true; + } + case WebKit::WebFilterOperation::FilterTypeColorMatrix: { + memcpy(matrix, op.matrix(), sizeof(SkScalar[20])); + return true; + } + default: + return false; + } +} + +class FilterBufferState { +public: + FilterBufferState(GrContext* grContext, const WebCore::FloatSize& size, unsigned textureId) + : m_grContext(grContext) + , m_currentTexture(0) + { + // Wrap the source texture in a Ganesh platform texture. + GrPlatformTextureDesc platformTextureDescription; + platformTextureDescription.fWidth = size.width(); + platformTextureDescription.fHeight = size.height(); + platformTextureDescription.fConfig = kSkia8888_PM_GrPixelConfig; + platformTextureDescription.fTextureHandle = textureId; + SkAutoTUnref<GrTexture> texture(grContext->createPlatformTexture(platformTextureDescription)); + // Place the platform texture inside an SkBitmap. + m_source.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height()); + m_source.setPixelRef(new SkGrTexturePixelRef(texture.get()))->unref(); + } + + ~FilterBufferState() { } + + bool init(int filterCount) + { + int scratchCount = std::min(2, filterCount); + GrTextureDesc desc; + desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit; + desc.fSampleCnt = 0; + desc.fWidth = m_source.width(); + desc.fHeight = m_source.height(); + desc.fConfig = kSkia8888_PM_GrPixelConfig; + for (int i = 0; i < scratchCount; ++i) { + GrAutoScratchTexture scratchTexture(m_grContext, desc, GrContext::kExact_ScratchTexMatch); + m_scratchTextures[i].reset(scratchTexture.detach()); + if (!m_scratchTextures[i].get()) + return false; + } + return true; + } + + SkCanvas* canvas() + { + if (!m_canvas.get()) + createCanvas(); + return m_canvas.get(); + } + + const SkBitmap& source() { return m_source; } + + void swap() + { + m_canvas->flush(); + m_canvas.reset(0); + m_device.reset(0); + + m_source.setPixelRef(new SkGrTexturePixelRef(m_scratchTextures[m_currentTexture].get()))->unref(); + m_currentTexture = 1 - m_currentTexture; + } + +private: + void createCanvas() + { + ASSERT(m_scratchTextures[m_currentTexture].get()); + m_device.reset(new SkGpuDevice(m_grContext, m_scratchTextures[m_currentTexture].get())); + m_canvas.reset(new SkCanvas(m_device.get())); + m_canvas->clear(0x0); + } + + GrContext* m_grContext; + SkBitmap m_source; + SkAutoTUnref<GrTexture> m_scratchTextures[2]; + int m_currentTexture; + SkAutoTUnref<SkGpuDevice> m_device; + SkAutoTUnref<SkCanvas> m_canvas; +}; + +} + +namespace WebCore { + +WebKit::WebFilterOperations CCRenderSurfaceFilters::optimize(const WebKit::WebFilterOperations& filters) +{ + WebKit::WebFilterOperations newList; + + SkScalar accumulatedColorMatrix[20]; + bool haveAccumulatedColorMatrix = false; + for (unsigned i = 0; i < filters.size(); ++i) { + const WebKit::WebFilterOperation& op = filters.at(i); + + // If the filter is a color matrix, we may be able to combine it with + // following filter(s) that also are color matrices. + SkScalar matrix[20]; + if (getColorMatrix(op, matrix)) { + if (haveAccumulatedColorMatrix) { + SkScalar newMatrix[20]; + multColorMatrix(matrix, accumulatedColorMatrix, newMatrix); + memcpy(accumulatedColorMatrix, newMatrix, sizeof(accumulatedColorMatrix)); + } else { + memcpy(accumulatedColorMatrix, matrix, sizeof(accumulatedColorMatrix)); + haveAccumulatedColorMatrix = true; + } + + // We can only combine matrices if clamping of color components + // would have no effect. + if (!matrixNeedsClamping(accumulatedColorMatrix)) + continue; + } + + if (haveAccumulatedColorMatrix) + newList.append(WebKit::WebFilterOperation::createColorMatrixFilter(accumulatedColorMatrix)); + haveAccumulatedColorMatrix = false; + + switch (op.type()) { + case WebKit::WebFilterOperation::FilterTypeBlur: + case WebKit::WebFilterOperation::FilterTypeDropShadow: + case WebKit::WebFilterOperation::FilterTypeZoom: + newList.append(op); + break; + case WebKit::WebFilterOperation::FilterTypeBrightness: + case WebKit::WebFilterOperation::FilterTypeContrast: + case WebKit::WebFilterOperation::FilterTypeGrayscale: + case WebKit::WebFilterOperation::FilterTypeSepia: + case WebKit::WebFilterOperation::FilterTypeSaturate: + case WebKit::WebFilterOperation::FilterTypeHueRotate: + case WebKit::WebFilterOperation::FilterTypeInvert: + case WebKit::WebFilterOperation::FilterTypeOpacity: + case WebKit::WebFilterOperation::FilterTypeColorMatrix: + break; + } + } + if (haveAccumulatedColorMatrix) + newList.append(WebKit::WebFilterOperation::createColorMatrixFilter(accumulatedColorMatrix)); + return newList; +} + +SkBitmap CCRenderSurfaceFilters::apply(const WebKit::WebFilterOperations& filters, unsigned textureId, const FloatSize& size, WebKit::WebGraphicsContext3D* context3D, GrContext* grContext) +{ + if (!context3D || !grContext) + return SkBitmap(); + + WebKit::WebFilterOperations optimizedFilters = optimize(filters); + FilterBufferState state(grContext, size, textureId); + if (!state.init(optimizedFilters.size())) + return SkBitmap(); + + for (unsigned i = 0; i < optimizedFilters.size(); ++i) { + const WebKit::WebFilterOperation& op = optimizedFilters.at(i); + SkCanvas* canvas = state.canvas(); + switch (op.type()) { + case WebKit::WebFilterOperation::FilterTypeColorMatrix: { + SkPaint paint; + paint.setColorFilter(new SkColorMatrixFilter(op.matrix()))->unref(); + canvas->drawBitmap(state.source(), 0, 0, &paint); + break; + } + case WebKit::WebFilterOperation::FilterTypeBlur: { + float stdDeviation = op.amount(); + SkAutoTUnref<SkImageFilter> filter(new SkBlurImageFilter(stdDeviation, stdDeviation)); + SkPaint paint; + paint.setImageFilter(filter.get()); + canvas->drawSprite(state.source(), 0, 0, &paint); + break; + } + case WebKit::WebFilterOperation::FilterTypeDropShadow: { + SkAutoTUnref<SkImageFilter> blurFilter(new SkBlurImageFilter(op.amount(), op.amount())); + SkAutoTUnref<SkColorFilter> colorFilter(SkColorFilter::CreateModeFilter(op.dropShadowColor(), SkXfermode::kSrcIn_Mode)); + SkPaint paint; + paint.setImageFilter(blurFilter.get()); + paint.setColorFilter(colorFilter.get()); + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); + canvas->saveLayer(0, &paint); + canvas->drawBitmap(state.source(), op.dropShadowOffset().x, -op.dropShadowOffset().y); + canvas->restore(); + canvas->drawBitmap(state.source(), 0, 0); + break; + } + case WebKit::WebFilterOperation::FilterTypeZoom: { + SkPaint paint; + SkAutoTUnref<SkImageFilter> zoomFilter( + new SkMagnifierImageFilter( + SkRect::MakeXYWH(op.zoomRect().x, + op.zoomRect().y, + op.zoomRect().width, + op.zoomRect().height), + op.amount())); + paint.setImageFilter(zoomFilter.get()); + canvas->saveLayer(0, &paint); + canvas->drawBitmap(state.source(), 0, 0); + break; + } + case WebKit::WebFilterOperation::FilterTypeBrightness: + case WebKit::WebFilterOperation::FilterTypeContrast: + case WebKit::WebFilterOperation::FilterTypeGrayscale: + case WebKit::WebFilterOperation::FilterTypeSepia: + case WebKit::WebFilterOperation::FilterTypeSaturate: + case WebKit::WebFilterOperation::FilterTypeHueRotate: + case WebKit::WebFilterOperation::FilterTypeInvert: + case WebKit::WebFilterOperation::FilterTypeOpacity: + ASSERT_NOT_REACHED(); + break; + } + state.swap(); + } + context3D->flush(); + return state.source(); +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCRenderSurfaceFilters.h b/cc/CCRenderSurfaceFilters.h new file mode 100644 index 0000000..92cd716 --- /dev/null +++ b/cc/CCRenderSurfaceFilters.h @@ -0,0 +1,34 @@ +// Copyright 2012 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. + + +#ifndef CCRenderSurfaceFilters_h +#define CCRenderSurfaceFilters_h + +#if USE(ACCELERATED_COMPOSITING) + +class GrContext; +class SkBitmap; + +namespace WebKit { +class WebFilterOperations; +class WebGraphicsContext3D; +} + +namespace WebCore { +class FloatSize; + +class CCRenderSurfaceFilters { +public: + static SkBitmap apply(const WebKit::WebFilterOperations& filters, unsigned textureId, const FloatSize&, WebKit::WebGraphicsContext3D*, GrContext*); + static WebKit::WebFilterOperations optimize(const WebKit::WebFilterOperations& filters); + +private: + CCRenderSurfaceFilters(); +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/CCRenderSurfaceFiltersTest.cpp b/cc/CCRenderSurfaceFiltersTest.cpp new file mode 100644 index 0000000..8ec4332 --- /dev/null +++ b/cc/CCRenderSurfaceFiltersTest.cpp @@ -0,0 +1,141 @@ +// Copyright 2012 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 "CCRenderSurfaceFilters.h" + +#include "CompositorFakeWebGraphicsContext3D.h" +#include <gtest/gtest.h> +#include <public/WebFilterOperation.h> +#include <public/WebFilterOperations.h> +#include <wtf/RefPtr.h> + +using namespace WebCore; +using namespace WebKit; + +namespace { + +// Checks whether op can be combined with a following color matrix. +bool isCombined(const WebFilterOperation& op) +{ + WebFilterOperations filters; + filters.append(op); + filters.append(WebFilterOperation::createBrightnessFilter(0)); // brightness(0) is identity. + WebFilterOperations optimized = CCRenderSurfaceFilters::optimize(filters); + return optimized.size() == 1; +} + +TEST(CCRenderSurfaceFiltersTest, testColorMatrixFiltersCombined) +{ + // Several filters should always combine for any amount between 0 and 1: + // grayscale, saturate, invert, contrast, opacity. + EXPECT_TRUE(isCombined(WebFilterOperation::createGrayscaleFilter(0))); + // Note that we use 0.3f to avoid "argument is truncated from 'double' to + // 'float'" warnings on Windows. 0.5 is exactly representable as a float, so + // there is no warning. + EXPECT_TRUE(isCombined(WebFilterOperation::createGrayscaleFilter(0.3f))); + EXPECT_TRUE(isCombined(WebFilterOperation::createGrayscaleFilter(0.5))); + EXPECT_TRUE(isCombined(WebFilterOperation::createGrayscaleFilter(1))); + + EXPECT_TRUE(isCombined(WebFilterOperation::createSaturateFilter(0))); + EXPECT_TRUE(isCombined(WebFilterOperation::createSaturateFilter(0.3f))); + EXPECT_TRUE(isCombined(WebFilterOperation::createSaturateFilter(0.5))); + EXPECT_TRUE(isCombined(WebFilterOperation::createSaturateFilter(1))); + + EXPECT_TRUE(isCombined(WebFilterOperation::createInvertFilter(0))); + EXPECT_TRUE(isCombined(WebFilterOperation::createInvertFilter(0.3f))); + EXPECT_TRUE(isCombined(WebFilterOperation::createInvertFilter(0.5))); + EXPECT_TRUE(isCombined(WebFilterOperation::createInvertFilter(1))); + + EXPECT_TRUE(isCombined(WebFilterOperation::createContrastFilter(0))); + EXPECT_TRUE(isCombined(WebFilterOperation::createContrastFilter(0.3f))); + EXPECT_TRUE(isCombined(WebFilterOperation::createContrastFilter(0.5))); + EXPECT_TRUE(isCombined(WebFilterOperation::createContrastFilter(1))); + + EXPECT_TRUE(isCombined(WebFilterOperation::createOpacityFilter(0))); + EXPECT_TRUE(isCombined(WebFilterOperation::createOpacityFilter(0.3f))); + EXPECT_TRUE(isCombined(WebFilterOperation::createOpacityFilter(0.5))); + EXPECT_TRUE(isCombined(WebFilterOperation::createOpacityFilter(1))); + + // Several filters should never combine: brightness(amount > 0), blur, drop-shadow. + EXPECT_FALSE(isCombined(WebFilterOperation::createBrightnessFilter(0.5))); + EXPECT_FALSE(isCombined(WebFilterOperation::createBrightnessFilter(1))); + EXPECT_FALSE(isCombined(WebFilterOperation::createBlurFilter(3))); + EXPECT_FALSE(isCombined(WebFilterOperation::createDropShadowFilter(WebPoint(2, 2), 3, 0xffffffff))); + + // sepia and hue may or may not combine depending on the value. + EXPECT_TRUE(isCombined(WebFilterOperation::createSepiaFilter(0))); + EXPECT_FALSE(isCombined(WebFilterOperation::createSepiaFilter(1))); + EXPECT_TRUE(isCombined(WebFilterOperation::createHueRotateFilter(0))); + EXPECT_FALSE(isCombined(WebFilterOperation::createHueRotateFilter(180))); + + float matrix1[20] = { + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + }; + EXPECT_TRUE(isCombined(WebFilterOperation::createColorMatrixFilter(matrix1))); + + float matrix2[20] = { + 1, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + }; + EXPECT_FALSE(isCombined(WebFilterOperation::createColorMatrixFilter(matrix2))); + + float matrix3[20] = { + 0.25, 0, 0, 0, 255*0.75, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + }; + EXPECT_TRUE(isCombined(WebFilterOperation::createColorMatrixFilter(matrix3))); + + float matrix4[20] = { + -0.25, 0.75, 0, 0, 255*0.25, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + }; + EXPECT_TRUE(isCombined(WebFilterOperation::createColorMatrixFilter(matrix4))); +} + +TEST(CCRenderSurfaceFiltersTest, testOptimize) +{ + WebFilterOperation combines(WebFilterOperation::createBrightnessFilter(0)); + WebFilterOperation doesntCombine(WebFilterOperation::createBrightnessFilter(1)); + + WebFilterOperations filters; + WebFilterOperations optimized = CCRenderSurfaceFilters::optimize(filters); + EXPECT_EQ(0u, optimized.size()); + + filters.append(combines); + optimized = CCRenderSurfaceFilters::optimize(filters); + EXPECT_EQ(1u, optimized.size()); + + filters.append(combines); + optimized = CCRenderSurfaceFilters::optimize(filters); + EXPECT_EQ(1u, optimized.size()); + + filters.append(doesntCombine); + optimized = CCRenderSurfaceFilters::optimize(filters); + EXPECT_EQ(1u, optimized.size()); + + filters.append(combines); + optimized = CCRenderSurfaceFilters::optimize(filters); + EXPECT_EQ(2u, optimized.size()); + + filters.append(doesntCombine); + optimized = CCRenderSurfaceFilters::optimize(filters); + EXPECT_EQ(2u, optimized.size()); + + filters.append(doesntCombine); + optimized = CCRenderSurfaceFilters::optimize(filters); + EXPECT_EQ(3u, optimized.size()); +} + +} // namespace diff --git a/cc/CCRenderSurfaceTest.cpp b/cc/CCRenderSurfaceTest.cpp new file mode 100644 index 0000000..c044993 --- /dev/null +++ b/cc/CCRenderSurfaceTest.cpp @@ -0,0 +1,116 @@ +// 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 "config.h" + +#include "CCRenderSurface.h" + +#include "CCLayerImpl.h" +#include "CCSharedQuadState.h" +#include "CCSingleThreadProxy.h" +#include "MockCCQuadCuller.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <public/WebTransformationMatrix.h> + +using namespace WebCore; +using WebKit::WebTransformationMatrix; + +namespace { + +#define EXECUTE_AND_VERIFY_SURFACE_CHANGED(codeToTest) \ + renderSurface->resetPropertyChangedFlag(); \ + codeToTest; \ + EXPECT_TRUE(renderSurface->surfacePropertyChanged()) + +#define EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(codeToTest) \ + renderSurface->resetPropertyChangedFlag(); \ + codeToTest; \ + EXPECT_FALSE(renderSurface->surfacePropertyChanged()) + +TEST(CCRenderSurfaceTest, verifySurfaceChangesAreTrackedProperly) +{ + // + // This test checks that surfacePropertyChanged() has the correct behavior. + // + + // This will fake that we are on the correct thread for testing purposes. + DebugScopedSetImplThread setImplThread; + + OwnPtr<CCLayerImpl> owningLayer = CCLayerImpl::create(1); + owningLayer->createRenderSurface(); + ASSERT_TRUE(owningLayer->renderSurface()); + CCRenderSurface* renderSurface = owningLayer->renderSurface(); + IntRect testRect = IntRect(IntPoint(3, 4), IntSize(5, 6)); + owningLayer->resetAllChangeTrackingForSubtree(); + + // Currently, the contentRect, clipRect, and owningLayer->layerPropertyChanged() are + // the only sources of change. + EXECUTE_AND_VERIFY_SURFACE_CHANGED(renderSurface->setClipRect(testRect)); + EXECUTE_AND_VERIFY_SURFACE_CHANGED(renderSurface->setContentRect(testRect)); + + owningLayer->setOpacity(0.5f); + EXPECT_TRUE(renderSurface->surfacePropertyChanged()); + owningLayer->resetAllChangeTrackingForSubtree(); + + // Setting the surface properties to the same values again should not be considered "change". + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->setClipRect(testRect)); + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->setContentRect(testRect)); + + OwnPtr<CCLayerImpl> dummyMask = CCLayerImpl::create(1); + WebTransformationMatrix dummyMatrix; + dummyMatrix.translate(1.0, 2.0); + + // The rest of the surface properties are either internal and should not cause change, + // or they are already accounted for by the owninglayer->layerPropertyChanged(). + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->setDrawOpacity(0.5)); + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->setDrawTransform(dummyMatrix)); + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->setReplicaDrawTransform(dummyMatrix)); + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->clearLayerList()); +} + +TEST(CCRenderSurfaceTest, sanityCheckSurfaceCreatesCorrectSharedQuadState) +{ + // This will fake that we are on the correct thread for testing purposes. + DebugScopedSetImplThread setImplThread; + + OwnPtr<CCLayerImpl> rootLayer = CCLayerImpl::create(1); + + OwnPtr<CCLayerImpl> owningLayer = CCLayerImpl::create(2); + owningLayer->createRenderSurface(); + ASSERT_TRUE(owningLayer->renderSurface()); + owningLayer->setRenderTarget(owningLayer.get()); + CCRenderSurface* renderSurface = owningLayer->renderSurface(); + + rootLayer->addChild(owningLayer.release()); + + IntRect contentRect = IntRect(IntPoint::zero(), IntSize(50, 50)); + IntRect clipRect = IntRect(IntPoint(5, 5), IntSize(40, 40)); + WebTransformationMatrix origin; + + origin.translate(30, 40); + + renderSurface->setDrawTransform(origin); + renderSurface->setContentRect(contentRect); + renderSurface->setClipRect(clipRect); + renderSurface->setDrawOpacity(1); + + CCQuadList quadList; + CCSharedQuadStateList sharedStateList; + MockCCQuadCuller mockQuadCuller(quadList, sharedStateList); + + bool forReplica = false; + renderSurface->appendQuads(mockQuadCuller, forReplica, 1); + + ASSERT_EQ(1u, sharedStateList.size()); + CCSharedQuadState* sharedQuadState = sharedStateList[0].get(); + + EXPECT_EQ(30, sharedQuadState->quadTransform.m41()); + EXPECT_EQ(40, sharedQuadState->quadTransform.m42()); + EXPECT_EQ(contentRect, IntRect(sharedQuadState->visibleContentRect)); + EXPECT_EQ(1, sharedQuadState->opacity); + EXPECT_FALSE(sharedQuadState->opaque); +} + +} // namespace diff --git a/cc/CCRenderer.h b/cc/CCRenderer.h new file mode 100644 index 0000000..629f1d7 --- /dev/null +++ b/cc/CCRenderer.h @@ -0,0 +1,90 @@ +// Copyright 2012 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. + +#ifndef CCRenderer_h +#define CCRenderer_h + +#include "CCLayerTreeHost.h" +#include "CCRenderPass.h" +#include "FloatQuad.h" +#include "IntRect.h" +#include <wtf/Noncopyable.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class CCScopedTexture; +class TextureCopier; +class TextureUploader; + +enum TextureUploaderOption { ThrottledUploader, UnthrottledUploader }; + +class CCRendererClient { +public: + virtual const IntSize& deviceViewportSize() const = 0; + virtual const CCLayerTreeSettings& settings() const = 0; + virtual void didLoseContext() = 0; + virtual void onSwapBuffersComplete() = 0; + virtual void releaseContentsTextures() = 0; + virtual void setFullRootLayerDamage() = 0; + virtual void setMemoryAllocationLimitBytes(size_t) = 0; +protected: + virtual ~CCRendererClient() { } +}; + +class CCRenderer { + WTF_MAKE_NONCOPYABLE(CCRenderer); +public: + // This enum defines the various resource pools for the CCResourceProvider + // where textures get allocated. + enum ResourcePool { + ImplPool = 1, // This pool is for textures that get allocated on the impl thread (e.g. RenderSurfaces). + ContentPool // This pool is for textures that get allocated on the main thread (e.g. tiles). + }; + + virtual ~CCRenderer() { } + + virtual const RendererCapabilities& capabilities() const = 0; + + const CCLayerTreeSettings& settings() const { return m_client->settings(); } + + const IntSize& viewportSize() { return m_client->deviceViewportSize(); } + int viewportWidth() { return viewportSize().width(); } + int viewportHeight() { return viewportSize().height(); } + + virtual void viewportChanged() { } + + virtual void decideRenderPassAllocationsForFrame(const CCRenderPassList&) { } + virtual bool haveCachedResourcesForRenderPassId(int) const { return false; } + + virtual void drawFrame(const CCRenderPassList&, const CCRenderPassIdHashMap&) = 0; + + // waits for rendering to finish + virtual void finish() = 0; + + virtual void doNoOp() { } + // puts backbuffer onscreen + virtual bool swapBuffers() = 0; + + virtual void getFramebufferPixels(void *pixels, const IntRect&) = 0; + + virtual TextureCopier* textureCopier() const = 0; + virtual TextureUploader* textureUploader() const = 0; + + virtual bool isContextLost() { return false; } + + virtual void setVisible(bool) = 0; + +protected: + explicit CCRenderer(CCRendererClient* client) + : m_client(client) + { + } + + CCRendererClient* m_client; +}; + +} + +#endif // CCRenderer_h diff --git a/cc/CCRendererGL.cpp b/cc/CCRendererGL.cpp new file mode 100644 index 0000000..dc20571 --- /dev/null +++ b/cc/CCRendererGL.cpp @@ -0,0 +1,1509 @@ +// 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" + +#if USE(ACCELERATED_COMPOSITING) +#include "CCRendererGL.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 "Extensions3D.h" +#include "FloatQuad.h" +#include "GeometryBinding.h" +#include "GrTexture.h" +#include "NotImplemented.h" +#include "PlatformColor.h" +#include "SkBitmap.h" +#include "SkColor.h" +#include "ThrottledTextureUploader.h" +#include "TraceEvent.h" +#include "UnthrottledTextureUploader.h" +#include <public/WebGraphicsContext3D.h> +#include <public/WebSharedGraphicsContext3D.h> +#include <public/WebVideoFrame.h> +#include <wtf/CurrentTime.h> +#include <wtf/MainThread.h> +#include <wtf/text/StringHash.h> + +using namespace std; +using WebKit::WebGraphicsContext3D; +using WebKit::WebGraphicsMemoryAllocation; +using WebKit::WebSharedGraphicsContext3D; +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +namespace { + +bool needsIOSurfaceReadbackWorkaround() +{ +#if OS(DARWIN) + return true; +#else + return false; +#endif +} + +} // anonymous namespace + +PassOwnPtr<CCRendererGL> CCRendererGL::create(CCRendererClient* client, CCResourceProvider* resourceProvider, TextureUploaderOption textureUploaderSetting) +{ + OwnPtr<CCRendererGL> renderer(adoptPtr(new CCRendererGL(client, resourceProvider, textureUploaderSetting))); + if (!renderer->initialize()) + return nullptr; + + return renderer.release(); +} + +CCRendererGL::CCRendererGL(CCRendererClient* client, + CCResourceProvider* resourceProvider, + TextureUploaderOption textureUploaderSetting) + : 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_isUsingBindUniform(false) + , m_visible(true) + , m_textureUploaderSetting(textureUploaderSetting) +{ + ASSERT(m_context); +} + +bool CCRendererGL::initialize() +{ + if (!m_context->makeContextCurrent()) + return false; + + m_context->setContextLostCallback(this); + m_context->pushGroupMarkerEXT("CompositorContext"); + + WebKit::WebString extensionsWebString = m_context->getString(GraphicsContext3D::EXTENSIONS); + String extensionsString(extensionsWebString.data(), extensionsWebString.length()); + Vector<String> extensionsList; + extensionsString.split(' ', extensionsList); + HashSet<String> extensions; + for (size_t i = 0; i < extensionsList.size(); ++i) + extensions.add(extensionsList[i]); + + if (settings().acceleratePainting && extensions.contains("GL_EXT_texture_format_BGRA8888") + && extensions.contains("GL_EXT_read_format_bgra")) + m_capabilities.usingAcceleratedPainting = true; + else + m_capabilities.usingAcceleratedPainting = false; + + + m_capabilities.contextHasCachedFrontBuffer = extensions.contains("GL_CHROMIUM_front_buffer_cached"); + + m_capabilities.usingPartialSwap = CCSettings::partialSwapEnabled() && extensions.contains("GL_CHROMIUM_post_sub_buffer"); + + // Use the swapBuffers callback only with the threaded proxy. + if (CCProxy::hasImplThread()) + m_capabilities.usingSwapCompleteCallback = extensions.contains("GL_CHROMIUM_swapbuffers_complete_callback"); + if (m_capabilities.usingSwapCompleteCallback) + m_context->setSwapBuffersCompleteCallbackCHROMIUM(this); + + m_capabilities.usingSetVisibility = extensions.contains("GL_CHROMIUM_set_visibility"); + + if (extensions.contains("GL_CHROMIUM_iosurface")) + ASSERT(extensions.contains("GL_ARB_texture_rectangle")); + + m_capabilities.usingGpuMemoryManager = extensions.contains("GL_CHROMIUM_gpu_memory_manager"); + if (m_capabilities.usingGpuMemoryManager) + m_context->setMemoryAllocationChangedCallbackCHROMIUM(this); + + m_capabilities.usingDiscardFramebuffer = extensions.contains("GL_CHROMIUM_discard_framebuffer"); + + m_capabilities.usingEglImage = extensions.contains("GL_OES_EGL_image_external"); + + GLC(m_context, m_context->getIntegerv(GraphicsContext3D::MAX_TEXTURE_SIZE, &m_capabilities.maxTextureSize)); + m_capabilities.bestTextureFormat = PlatformColor::bestTextureFormat(m_context, extensions.contains("GL_EXT_texture_format_BGRA8888")); + + m_isUsingBindUniform = extensions.contains("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() +{ + ASSERT(CCProxy::isImplThread()); + m_context->setSwapBuffersCompleteCallbackCHROMIUM(0); + m_context->setMemoryAllocationChangedCallbackCHROMIUM(0); + m_context->setContextLostCallback(0); + cleanupSharedObjects(); +} + +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 != GraphicsContext3D::NO_ERROR) + LOG_ERROR("GL command failed: File: %s\n\tLine %d\n\tcommand: %s, error %x\n", file, line, command, static_cast<int>(error)); +} + +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); +} + +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)); + +#if defined(NDEBUG) + if (frame.currentRenderPass->hasTransparentBackground()) +#endif + m_context->clear(GraphicsContext3D::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(GraphicsContext3D::DEPTH_TEST)); + GLC(m_context, m_context->disable(GraphicsContext3D::CULL_FACE)); + GLC(m_context, m_context->colorMask(true, true, true, true)); + GLC(m_context, m_context->enable(GraphicsContext3D::BLEND)); + GLC(m_context, m_context->blendFunc(GraphicsContext3D::ONE, GraphicsContext3D::ONE_MINUS_SRC_ALPHA)); +} + +void CCRendererGL::doNoOp() +{ + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::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(GraphicsContext3D::BLEND)); + else + GLC(m_context, m_context->disable(GraphicsContext3D::BLEND)); + + switch (quad->material()) { + case CCDrawQuad::Invalid: + ASSERT_NOT_REACHED(); + 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(); + ASSERT(program && program->initialized()); + GLC(context(), context()->useProgram(program->program())); + + IntRect tileRect = quad->quadRect(); + float texOffsetX = tileRect.x(); + float texOffsetY = tileRect.y(); + float texScaleX = tileRect.width(); + float texScaleY = tileRect.height(); + GLC(context(), context()->uniform4f(program->fragmentShader().texTransformLocation(), texOffsetX, texOffsetY, texScaleX, texScaleY)); + + const int checkerboardWidth = 16; + float frequency = 1.0 / checkerboardWidth; + + 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(); + ASSERT(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(GraphicsContext3D::LINE_LOOP, 4, GraphicsContext3D::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; +} + +PassOwnPtr<CCScopedTexture> 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 nullptr; + + // 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 nullptr; + ASSERT(!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()); + + OwnPtr<CCScopedTexture> deviceBackgroundTexture = CCScopedTexture::create(m_resourceProvider); + if (!getFramebufferTexture(deviceBackgroundTexture.get(), deviceRect)) + return nullptr; + + SkBitmap filteredDeviceBackground = applyFilters(this, filters, deviceBackgroundTexture.get()); + if (!filteredDeviceBackground.getTexture()) + return nullptr; + + GrTexture* texture = reinterpret_cast<GrTexture*>(filteredDeviceBackground.getTexture()); + int filteredDeviceBackgroundTextureId = texture->getTextureHandle(); + + OwnPtr<CCScopedTexture> backgroundTexture = CCScopedTexture::create(m_resourceProvider); + if (!backgroundTexture->allocate(CCRenderer::ImplPool, quad->quadRect().size(), GraphicsContext3D::RGBA, CCResourceProvider::TextureUsageFramebuffer)) + return nullptr; + + 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 nullptr; + return backgroundTexture.release(); +} + +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()); + ASSERT(renderPass); + if (!renderPass) + return; + + WebTransformationMatrix renderMatrix = quad->quadTransform(); + renderMatrix.translate(0.5 * quad->quadRect().width() + quad->quadRect().x(), 0.5 * quad->quadRect().height() + quad->quadRect().y()); + WebTransformationMatrix deviceMatrix = renderMatrix; + deviceMatrix.scaleNonUniform(quad->quadRect().width(), quad->quadRect().height()); + WebTransformationMatrix contentsDeviceTransform = WebTransformationMatrix(frame.windowMatrix * frame.projectionMatrix * deviceMatrix).to2dTransform(); + + // Can only draw surface if device matrix is invertible. + if (!contentsDeviceTransform.isInvertible()) + return; + + OwnPtr<CCScopedTexture> 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); + OwnPtr<CCResourceProvider::ScopedReadLockGL> contentsResourceLock; + unsigned contentsTextureId = 0; + if (filterBitmap.getTexture()) { + GrTexture* texture = reinterpret_cast<GrTexture*>(filterBitmap.getTexture()); + contentsTextureId = texture->getTextureHandle(); + } else { + contentsResourceLock = adoptPtr(new CCResourceProvider::ScopedReadLockGL(m_resourceProvider, contentsTexture->id())); + contentsTextureId = contentsResourceLock->textureId(); + } + + // Draw the background texture if there is one. + if (backgroundTexture) { + ASSERT(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); + ASSERT(!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(); + } + + OwnPtr<CCResourceProvider::ScopedReadLockGL> maskResourceLock; + unsigned maskTextureId = 0; + if (quad->maskResourceId()) { + maskResourceLock = adoptPtr(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(GraphicsContext3D::TEXTURE0)); + context()->bindTexture(GraphicsContext3D::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) { + ASSERT(shaderMaskTexCoordScaleLocation != 1); + ASSERT(shaderMaskTexCoordOffsetLocation != 1); + GLC(context(), context()->activeTexture(GraphicsContext3D::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(GraphicsContext3D::TEXTURE_2D, maskTextureId); + GLC(context(), context()->activeTexture(GraphicsContext3D::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); + ASSERT(!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<class T> +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); + ASSERT(!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(GraphicsContext3D::TEXTURE0)); + CCResourceProvider::ScopedReadLockGL quadResourceLock(m_resourceProvider, quad->resourceId()); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, quadResourceLock.textureId())); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, quad->textureFilter())); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::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); + ASSERT(!clipped); + bottomLeft = CCMathUtil::mapPoint(deviceTransform, bottomLeft, clipped); + ASSERT(!clipped); + topLeft = CCMathUtil::mapPoint(deviceTransform, topLeft, clipped); + ASSERT(!clipped); + topRight = CCMathUtil::mapPoint(deviceTransform, topRight, clipped); + ASSERT(!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 ASSERT(!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(); + ASSERT(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(GraphicsContext3D::TEXTURE1)); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, yPlaneLock.textureId())); + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE2)); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, uPlaneLock.textureId())); + GLC(context(), context()->activeTexture(GraphicsContext3D::TEXTURE3)); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, vPlaneLock.textureId())); + + GLC(context(), context()->useProgram(program->program())); + + float yWidthScaleFactor = static_cast<float>(yPlane.visibleSize.width()) / yPlane.size.width(); + // Arbitrarily take the u sizes because u and v dimensions are identical. + float uvWidthScaleFactor = static_cast<float>(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(GraphicsContext3D::TEXTURE0)); +} + +void CCRendererGL::drawStreamVideoQuad(const DrawingFrame& frame, const CCStreamVideoDrawQuad* quad) +{ + static float glMatrix[16]; + + ASSERT(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(GraphicsContext3D::TEXTURE0)); + GLC(context(), context()->bindTexture(Extensions3DChromium::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<class Program> void set(Program* program) + { + ASSERT(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<class Program> void set(Program* program) + { + TextureProgramBinding::set(program); + texTransformLocation = program->vertexShader().texTransformLocation(); + } + int texTransformLocation; +}; + +void CCRendererGL::drawTextureQuad(const DrawingFrame& frame, const CCTextureDrawQuad* quad) +{ + ASSERT(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(GraphicsContext3D::TEXTURE0)); + CCResourceProvider::ScopedReadLockGL quadResourceLock(m_resourceProvider, quad->resourceId()); + GLC(context(), context()->bindTexture(GraphicsContext3D::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(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::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(GraphicsContext3D::SRC_ALPHA, GraphicsContext3D::ONE_MINUS_SRC_ALPHA, GraphicsContext3D::ZERO, GraphicsContext3D::ONE)); + } + + setShaderOpacity(quad->opacity(), binding.alphaLocation); + drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), binding.matrixLocation); + + if (!quad->premultipliedAlpha()) + GLC(m_context, m_context->blendFunc(GraphicsContext3D::ONE, GraphicsContext3D::ONE_MINUS_SRC_ALPHA)); +} + +void CCRendererGL::drawIOSurfaceQuad(const DrawingFrame& frame, const CCIOSurfaceDrawQuad* quad) +{ + ASSERT(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(GraphicsContext3D::TEXTURE0)); + GLC(context(), context()->bindTexture(Extensions3D::TEXTURE_RECTANGLE_ARB, quad->ioSurfaceTextureId())); + + setShaderOpacity(quad->opacity(), binding.alphaLocation); + drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), binding.matrixLocation); + + GLC(context(), context()->bindTexture(Extensions3D::TEXTURE_RECTANGLE_ARB, 0)); +} + +void CCRendererGL::finishDrawingFrame(DrawingFrame& frame) +{ + m_currentFramebufferLock.clear(); + m_swapBufferRect.unite(enclosingIntRect(frame.rootDamageRect)); + + GLC(m_context, m_context->disable(GraphicsContext3D::SCISSOR_TEST)); + GLC(m_context, m_context->disable(GraphicsContext3D::BLEND)); +} + +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(GraphicsContext3D::TRIANGLES, 6, GraphicsContext3D::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(GraphicsContext3D::TEXTURE0)); + GLC(context(), context()->bindTexture(GraphicsContext3D::TEXTURE_2D, textureId)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE)); + GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::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() +{ + ASSERT(m_visible); + ASSERT(!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()) { + ASSERT(CCProxy::isMainThread()); + DebugScopedSetImplThread impl; + onMemoryAllocationChangedOnImplThread(allocation); + } else { + ASSERT(CCProxy::isImplThread()); + onMemoryAllocationChangedOnImplThread(allocation); + } +} + +void CCRendererGL::onMemoryAllocationChangedOnImplThread(WebKit::WebGraphicsMemoryAllocation allocation) +{ + if (m_visible && !allocation.gpuResourceSizeInBytes) + return; + + if (!allocation.suggestHaveBackbuffer && !m_visible) + discardFramebuffer(); + + if (!allocation.gpuResourceSizeInBytes) { + releaseRenderPassTextures(); + m_client->releaseContentsTextures(); + GLC(m_context, m_context->flush()); + } else + m_client->setMemoryAllocationLimitBytes(allocation.gpuResourceSizeInBytes); +} + +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(GraphicsContext3D::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) +{ + ASSERT(rect.maxX() <= viewportWidth() && rect.maxY() <= viewportHeight()); + + if (!pixels) + return; + + makeContextCurrent(); + + bool doWorkaround = needsIOSurfaceReadbackWorkaround(); + + Platform3DObject temporaryTexture = 0; + Platform3DObject 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. <rdar://problem/10949687> + + temporaryTexture = m_context->createTexture(); + GLC(m_context, m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, temporaryTexture)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE)); + // Copy the contents of the current (IOSurface-backed) framebuffer into a temporary texture. + GLC(m_context, m_context->copyTexImage2D(GraphicsContext3D::TEXTURE_2D, 0, GraphicsContext3D::RGBA, 0, 0, rect.maxX(), rect.maxY(), 0)); + temporaryFBO = m_context->createFramebuffer(); + // Attach this texture to an FBO, and perform the readback from that FBO. + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, temporaryFBO)); + GLC(m_context, m_context->framebufferTexture2D(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::TEXTURE_2D, temporaryTexture, 0)); + + ASSERT(m_context->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) == GraphicsContext3D::FRAMEBUFFER_COMPLETE); + } + + GLC(m_context, m_context->readPixels(rect.x(), rect.y(), rect.width(), rect.height(), + GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, pixels)); + + if (doWorkaround) { + // Clean up. + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, 0)); + GLC(m_context, m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, 0)); + GLC(m_context, m_context->deleteFramebuffer(temporaryFBO)); + GLC(m_context, m_context->deleteTexture(temporaryTexture)); + } + + if (!m_visible) { + TRACE_EVENT0("cc", "CCRendererGL::getFramebufferPixels dropping resources after readback"); + discardFramebuffer(); + releaseRenderPassTextures(); + m_client->releaseContentsTextures(); + GLC(m_context, m_context->flush()); + } +} + +bool CCRendererGL::getFramebufferTexture(CCScopedTexture* texture, const IntRect& deviceRect) +{ + ASSERT(!texture->id() || (texture->size() == deviceRect.size() && texture->format() == GraphicsContext3D::RGB)); + + if (!texture->id() && !texture->allocate(CCRenderer::ImplPool, deviceRect.size(), GraphicsContext3D::RGB, CCResourceProvider::TextureUsageAny)) + return false; + + CCResourceProvider::ScopedWriteLockGL lock(m_resourceProvider, texture->id()); + GLC(m_context, m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, lock.textureId())); + GLC(m_context, m_context->copyTexImage2D(GraphicsContext3D::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) +{ + ASSERT(texture->id()); + frame.currentRenderPass = 0; + frame.currentTexture = texture; + + return bindFramebufferToTexture(frame, texture, viewportRect); +} + +void CCRendererGL::bindFramebufferToOutputSurface(DrawingFrame& frame) +{ + m_currentFramebufferLock.clear(); + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, 0)); +} + +bool CCRendererGL::bindFramebufferToTexture(DrawingFrame& frame, const CCScopedTexture* texture, const IntRect& framebufferRect) +{ + ASSERT(texture->id()); + + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_offscreenFramebufferId)); + m_currentFramebufferLock = adoptPtr(new CCResourceProvider::ScopedWriteLockGL(m_resourceProvider, texture->id())); + unsigned textureId = m_currentFramebufferLock->textureId(); + GLC(m_context, m_context->framebufferTexture2D(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::TEXTURE_2D, textureId, 0)); + +#if !defined ( NDEBUG ) + if (m_context->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) != GraphicsContext3D::FRAMEBUFFER_COMPLETE) { + ASSERT_NOT_REACHED(); + return false; + } +#endif + + initializeMatrices(frame, framebufferRect, false); + setDrawViewportSize(framebufferRect.size()); + + return true; +} + +void CCRendererGL::enableScissorTestRect(const IntRect& scissorRect) +{ + GLC(m_context, m_context->enable(GraphicsContext3D::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(GraphicsContext3D::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 = adoptPtr(new GeometryBinding(m_context, quadVertexRect())); + m_renderPassProgram = adoptPtr(new RenderPassProgram(m_context)); + m_tileProgram = adoptPtr(new TileProgram(m_context)); + m_tileProgramOpaque = adoptPtr(new TileProgramOpaque(m_context)); + + GLC(m_context, m_context->flush()); + + m_textureCopier = AcceleratedTextureCopier::create(m_context, m_isUsingBindUniform); + if (m_textureUploaderSetting == ThrottledUploader) + m_textureUploader = ThrottledTextureUploader::create(m_context); + else + m_textureUploader = UnthrottledTextureUploader::create(); + + return true; +} + +const CCRendererGL::TileCheckerboardProgram* CCRendererGL::tileCheckerboardProgram() +{ + if (!m_tileCheckerboardProgram) + m_tileCheckerboardProgram = adoptPtr(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 = adoptPtr(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() +{ + ASSERT(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 = adoptPtr(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 = adoptPtr(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 = adoptPtr(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() +{ + ASSERT(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() +{ + ASSERT(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 = adoptPtr(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 = adoptPtr(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 = adoptPtr(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 = adoptPtr(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 = adoptPtr(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 = adoptPtr(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 = adoptPtr(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 = adoptPtr(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 = adoptPtr(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.clear(); + + 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)); + + m_textureCopier.clear(); + m_textureUploader.clear(); + + releaseRenderPassTextures(); +} + +bool CCRendererGL::isContextLost() +{ + return (m_context->getGraphicsResetStatusARB() != GraphicsContext3D::NO_ERROR); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCRendererGL.h b/cc/CCRendererGL.h new file mode 100644 index 0000000..202d412 --- /dev/null +++ b/cc/CCRendererGL.h @@ -0,0 +1,245 @@ +// 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. + + +#ifndef CCRendererGL_h +#define CCRendererGL_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCCheckerboardDrawQuad.h" +#include "CCDebugBorderDrawQuad.h" +#include "CCDirectRenderer.h" +#include "CCIOSurfaceDrawQuad.h" +#include "CCRenderPassDrawQuad.h" +#include "CCRenderer.h" +#include "CCSolidColorDrawQuad.h" +#include "CCStreamVideoDrawQuad.h" +#include "CCTextureDrawQuad.h" +#include "CCTileDrawQuad.h" +#include "CCYUVVideoDrawQuad.h" +#include "Extensions3DChromium.h" +#include "TextureCopier.h" +#include <wtf/PassOwnPtr.h> + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace WebCore { + +class CCScopedTexture; +class GeometryBinding; +class ScopedEnsureFramebufferAllocation; + +// Class that handles drawing of composited render layers using GL. +class CCRendererGL : public CCDirectRenderer, + public WebKit::WebGraphicsContext3D::WebGraphicsSwapBuffersCompleteCallbackCHROMIUM, + public WebKit::WebGraphicsContext3D::WebGraphicsMemoryAllocationChangedCallbackCHROMIUM , + public WebKit::WebGraphicsContext3D::WebGraphicsContextLostCallback { + WTF_MAKE_NONCOPYABLE(CCRendererGL); +public: + static PassOwnPtr<CCRendererGL> create(CCRendererClient*, CCResourceProvider*, TextureUploaderOption); + + virtual ~CCRendererGL(); + + virtual const RendererCapabilities& capabilities() const OVERRIDE { return m_capabilities; } + + WebKit::WebGraphicsContext3D* context(); + + virtual void viewportChanged() OVERRIDE; + + const FloatQuad& sharedGeometryQuad() const { return m_sharedGeometryQuad; } + + // waits for rendering to finish + virtual void finish() OVERRIDE; + + virtual void doNoOp() OVERRIDE; + // puts backbuffer onscreen + virtual bool swapBuffers() OVERRIDE; + + static void debugGLCall(WebKit::WebGraphicsContext3D*, const char* command, const char* file, int line); + + const GeometryBinding* sharedGeometry() const { return m_sharedGeometry.get(); } + + virtual void getFramebufferPixels(void *pixels, const IntRect&) OVERRIDE; + bool getFramebufferTexture(CCScopedTexture*, const IntRect& deviceRect); + + virtual TextureCopier* textureCopier() const OVERRIDE { return m_textureCopier.get(); } + virtual TextureUploader* textureUploader() const OVERRIDE { return m_textureUploader.get(); } + + virtual bool isContextLost() OVERRIDE; + + virtual void setVisible(bool) OVERRIDE; + +protected: + CCRendererGL(CCRendererClient*, CCResourceProvider*, TextureUploaderOption); + + bool isFramebufferDiscarded() const { return m_isFramebufferDiscarded; } + bool initialize(); + + void releaseRenderPassTextures(); + + virtual void bindFramebufferToOutputSurface(DrawingFrame&) OVERRIDE; + virtual bool bindFramebufferToTexture(DrawingFrame&, const CCScopedTexture*, const IntRect& framebufferRect) OVERRIDE; + virtual void setDrawViewportSize(const IntSize&) OVERRIDE; + virtual void enableScissorTestRect(const IntRect& scissorRect) OVERRIDE; + virtual void disableScissorTest() OVERRIDE; + virtual void clearFramebuffer(DrawingFrame&) OVERRIDE; + virtual void drawQuad(DrawingFrame&, const CCDrawQuad*) OVERRIDE; + virtual void beginDrawingFrame(DrawingFrame&) OVERRIDE; + virtual void finishDrawingFrame(DrawingFrame&) OVERRIDE; + +private: + static void toGLMatrix(float*, const WebKit::WebTransformationMatrix&); + + void drawCheckerboardQuad(const DrawingFrame&, const CCCheckerboardDrawQuad*); + void drawDebugBorderQuad(const DrawingFrame&, const CCDebugBorderDrawQuad*); + PassOwnPtr<CCScopedTexture> drawBackgroundFilters(DrawingFrame&, const CCRenderPassDrawQuad*, const WebKit::WebFilterOperations&, const WebKit::WebTransformationMatrix& deviceTransform); + void drawRenderPassQuad(DrawingFrame&, const CCRenderPassDrawQuad*); + void drawSolidColorQuad(const DrawingFrame&, const CCSolidColorDrawQuad*); + void drawStreamVideoQuad(const DrawingFrame&, const CCStreamVideoDrawQuad*); + void drawTextureQuad(const DrawingFrame&, const CCTextureDrawQuad*); + void drawIOSurfaceQuad(const DrawingFrame&, const CCIOSurfaceDrawQuad*); + void drawTileQuad(const DrawingFrame&, const CCTileDrawQuad*); + void drawYUVVideoQuad(const DrawingFrame&, const CCYUVVideoDrawQuad*); + + void setShaderOpacity(float opacity, int alphaLocation); + void setShaderFloatQuad(const FloatQuad&, int quadLocation); + void drawQuadGeometry(const DrawingFrame&, const WebKit::WebTransformationMatrix& drawTransform, const FloatRect& quadRect, int matrixLocation); + + void copyTextureToFramebuffer(const DrawingFrame&, int textureId, const IntRect&, const WebKit::WebTransformationMatrix& drawMatrix); + + bool useScopedTexture(DrawingFrame&, const CCScopedTexture*, const IntRect& viewportRect); + + bool makeContextCurrent(); + + bool initializeSharedObjects(); + void cleanupSharedObjects(); + + // WebKit::WebGraphicsContext3D::WebGraphicsSwapBuffersCompleteCallbackCHROMIUM implementation. + virtual void onSwapBuffersComplete() OVERRIDE; + + // WebKit::WebGraphicsContext3D::WebGraphicsMemoryAllocationChangedCallbackCHROMIUM implementation. + virtual void onMemoryAllocationChanged(WebKit::WebGraphicsMemoryAllocation) OVERRIDE; + void onMemoryAllocationChangedOnImplThread(WebKit::WebGraphicsMemoryAllocation); + void discardFramebuffer(); + void ensureFramebuffer(); + + // WebGraphicsContext3D::WebGraphicsContextLostCallback implementation. + virtual void onContextLost() OVERRIDE; + + RendererCapabilities m_capabilities; + + unsigned m_offscreenFramebufferId; + + OwnPtr<GeometryBinding> m_sharedGeometry; + FloatQuad m_sharedGeometryQuad; + + // This block of bindings defines all of the programs used by the compositor itself. + + // Tiled layer shaders. + typedef ProgramBinding<VertexShaderTile, FragmentShaderRGBATexAlpha> TileProgram; + typedef ProgramBinding<VertexShaderTile, FragmentShaderRGBATexClampAlphaAA> TileProgramAA; + typedef ProgramBinding<VertexShaderTile, FragmentShaderRGBATexClampSwizzleAlphaAA> TileProgramSwizzleAA; + typedef ProgramBinding<VertexShaderTile, FragmentShaderRGBATexOpaque> TileProgramOpaque; + typedef ProgramBinding<VertexShaderTile, FragmentShaderRGBATexSwizzleAlpha> TileProgramSwizzle; + typedef ProgramBinding<VertexShaderTile, FragmentShaderRGBATexSwizzleOpaque> TileProgramSwizzleOpaque; + typedef ProgramBinding<VertexShaderPosTex, FragmentShaderCheckerboard> TileCheckerboardProgram; + + // Render surface shaders. + typedef ProgramBinding<VertexShaderPosTex, FragmentShaderRGBATexAlpha> RenderPassProgram; + typedef ProgramBinding<VertexShaderPosTex, FragmentShaderRGBATexAlphaMask> RenderPassMaskProgram; + typedef ProgramBinding<VertexShaderQuad, FragmentShaderRGBATexAlphaAA> RenderPassProgramAA; + typedef ProgramBinding<VertexShaderQuad, FragmentShaderRGBATexAlphaMaskAA> RenderPassMaskProgramAA; + + // Texture shaders. + typedef ProgramBinding<VertexShaderPosTexTransform, FragmentShaderRGBATexAlpha> TextureProgram; + typedef ProgramBinding<VertexShaderPosTexTransform, FragmentShaderRGBATexFlipAlpha> TextureProgramFlip; + typedef ProgramBinding<VertexShaderPosTexTransform, FragmentShaderRGBATexRectAlpha> TextureIOSurfaceProgram; + + // Video shaders. + typedef ProgramBinding<VertexShaderVideoTransform, FragmentShaderOESImageExternal> VideoStreamTextureProgram; + typedef ProgramBinding<VertexShaderPosTexYUVStretch, FragmentShaderYUVVideo> VideoYUVProgram; + + // Special purpose / effects shaders. + typedef ProgramBinding<VertexShaderPos, FragmentShaderColor> SolidColorProgram; + + const TileProgram* tileProgram(); + const TileProgramOpaque* tileProgramOpaque(); + const TileProgramAA* tileProgramAA(); + const TileProgramSwizzle* tileProgramSwizzle(); + const TileProgramSwizzleOpaque* tileProgramSwizzleOpaque(); + const TileProgramSwizzleAA* tileProgramSwizzleAA(); + const TileCheckerboardProgram* tileCheckerboardProgram(); + + const RenderPassProgram* renderPassProgram(); + const RenderPassProgramAA* renderPassProgramAA(); + const RenderPassMaskProgram* renderPassMaskProgram(); + const RenderPassMaskProgramAA* renderPassMaskProgramAA(); + + const TextureProgram* textureProgram(); + const TextureProgramFlip* textureProgramFlip(); + const TextureIOSurfaceProgram* textureIOSurfaceProgram(); + + const VideoYUVProgram* videoYUVProgram(); + const VideoStreamTextureProgram* videoStreamTextureProgram(); + + const SolidColorProgram* solidColorProgram(); + + OwnPtr<TileProgram> m_tileProgram; + OwnPtr<TileProgramOpaque> m_tileProgramOpaque; + OwnPtr<TileProgramAA> m_tileProgramAA; + OwnPtr<TileProgramSwizzle> m_tileProgramSwizzle; + OwnPtr<TileProgramSwizzleOpaque> m_tileProgramSwizzleOpaque; + OwnPtr<TileProgramSwizzleAA> m_tileProgramSwizzleAA; + OwnPtr<TileCheckerboardProgram> m_tileCheckerboardProgram; + + OwnPtr<RenderPassProgram> m_renderPassProgram; + OwnPtr<RenderPassProgramAA> m_renderPassProgramAA; + OwnPtr<RenderPassMaskProgram> m_renderPassMaskProgram; + OwnPtr<RenderPassMaskProgramAA> m_renderPassMaskProgramAA; + + OwnPtr<TextureProgram> m_textureProgram; + OwnPtr<TextureProgramFlip> m_textureProgramFlip; + OwnPtr<TextureIOSurfaceProgram> m_textureIOSurfaceProgram; + + OwnPtr<VideoYUVProgram> m_videoYUVProgram; + OwnPtr<VideoStreamTextureProgram> m_videoStreamTextureProgram; + + OwnPtr<SolidColorProgram> m_solidColorProgram; + + OwnPtr<AcceleratedTextureCopier> m_textureCopier; + OwnPtr<TextureUploader> m_textureUploader; + + WebKit::WebGraphicsContext3D* m_context; + + IntRect m_swapBufferRect; + bool m_isViewportChanged; + bool m_isFramebufferDiscarded; + bool m_isUsingBindUniform; + bool m_visible; + TextureUploaderOption m_textureUploaderSetting; + + OwnPtr<CCResourceProvider::ScopedWriteLockGL> m_currentFramebufferLock; +}; + + +// Setting DEBUG_GL_CALLS to 1 will call glGetError() after almost every GL +// call made by the compositor. Useful for debugging rendering issues but +// will significantly degrade performance. +#define DEBUG_GL_CALLS 0 + +#if DEBUG_GL_CALLS && !defined ( NDEBUG ) +#define GLC(context, x) (x, CCRendererGL::debugGLCall(&*context, #x, __FILE__, __LINE__)) +#else +#define GLC(context, x) (x) +#endif + + +} + +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/CCRenderingStats.h b/cc/CCRenderingStats.h new file mode 100644 index 0000000..5926f6c --- /dev/null +++ b/cc/CCRenderingStats.h @@ -0,0 +1,30 @@ +// Copyright 2012 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. + +#ifndef CCRenderingStats_h +#define CCRenderingStats_h + +namespace WebCore { + +struct CCRenderingStats { + // FIXME: Rename these to animationFrameCount and screenFrameCount, crbug.com/138641. + int numAnimationFrames; + int numFramesSentToScreen; + int droppedFrameCount; + double totalPaintTimeInSeconds; + double totalRasterizeTimeInSeconds; + + CCRenderingStats() + : numAnimationFrames(0) + , numFramesSentToScreen(0) + , droppedFrameCount(0) + , totalPaintTimeInSeconds(0) + , totalRasterizeTimeInSeconds(0) + { + } +}; + +} + +#endif diff --git a/cc/CCResourceProvider.cpp b/cc/CCResourceProvider.cpp new file mode 100644 index 0000000..2f575ed --- /dev/null +++ b/cc/CCResourceProvider.cpp @@ -0,0 +1,529 @@ +// Copyright 2012 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 "CCResourceProvider.h" + +#include "CCProxy.h" +#include "CCRendererGL.h" // For the GLC() macro. +#include "Extensions3DChromium.h" +#include "IntRect.h" +#include "LayerTextureSubImage.h" +#include <limits.h> +#include <public/WebGraphicsContext3D.h> +#include <wtf/HashSet.h> + +using WebKit::WebGraphicsContext3D; + +namespace WebCore { + +static GC3Denum textureToStorageFormat(GC3Denum textureFormat) +{ + GC3Denum storageFormat = Extensions3D::RGBA8_OES; + switch (textureFormat) { + case GraphicsContext3D::RGBA: + break; + case Extensions3D::BGRA_EXT: + storageFormat = Extensions3DChromium::BGRA8_EXT; + break; + default: + ASSERT_NOT_REACHED(); + break; + } + + return storageFormat; +} + +static bool isTextureFormatSupportedForStorage(GC3Denum format) +{ + return (format == GraphicsContext3D::RGBA || format == Extensions3D::BGRA_EXT); +} + +PassOwnPtr<CCResourceProvider> CCResourceProvider::create(CCGraphicsContext* context) +{ + OwnPtr<CCResourceProvider> resourceProvider(adoptPtr(new CCResourceProvider(context))); + if (!resourceProvider->initialize()) + return nullptr; + return resourceProvider.release(); +} + +CCResourceProvider::~CCResourceProvider() +{ +} + +WebGraphicsContext3D* CCResourceProvider::graphicsContext3D() +{ + ASSERT(CCProxy::isImplThread()); + return m_context->context3D(); +} + +bool CCResourceProvider::inUseByConsumer(ResourceId id) +{ + ASSERT(CCProxy::isImplThread()); + ResourceMap::iterator it = m_resources.find(id); + ASSERT(it != m_resources.end()); + return !!it->second.lockForReadCount || it->second.exported; +} + +CCResourceProvider::ResourceId CCResourceProvider::createResource(int pool, const IntSize& size, GC3Denum format, TextureUsageHint hint) +{ + switch (m_defaultResourceType) { + case GLTexture: + return createGLTexture(pool, size, format, hint); + case Bitmap: + ASSERT(format == GraphicsContext3D::RGBA); + return createBitmap(pool, size); + } + + CRASH(); + return 0; +} + +CCResourceProvider::ResourceId CCResourceProvider::createGLTexture(int pool, const IntSize& size, GC3Denum format, TextureUsageHint hint) +{ + ASSERT(CCProxy::isImplThread()); + unsigned textureId = 0; + WebGraphicsContext3D* context3d = m_context->context3D(); + ASSERT(context3d); + GLC(context3d, textureId = context3d->createTexture()); + GLC(context3d, context3d->bindTexture(GraphicsContext3D::TEXTURE_2D, textureId)); + GLC(context3d, context3d->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR)); + GLC(context3d, context3d->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR)); + GLC(context3d, context3d->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_S, GraphicsContext3D::CLAMP_TO_EDGE)); + GLC(context3d, context3d->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_WRAP_T, GraphicsContext3D::CLAMP_TO_EDGE)); + + if (m_useTextureUsageHint && hint == TextureUsageFramebuffer) + GLC(context3d, context3d->texParameteri(GraphicsContext3D::TEXTURE_2D, Extensions3DChromium::GL_TEXTURE_USAGE_ANGLE, Extensions3DChromium::GL_FRAMEBUFFER_ATTACHMENT_ANGLE)); + if (m_useTextureStorageExt && isTextureFormatSupportedForStorage(format)) { + GC3Denum storageFormat = textureToStorageFormat(format); + GLC(context3d, context3d->texStorage2DEXT(GraphicsContext3D::TEXTURE_2D, 1, storageFormat, size.width(), size.height())); + } else + GLC(context3d, context3d->texImage2D(GraphicsContext3D::TEXTURE_2D, 0, format, size.width(), size.height(), 0, format, GraphicsContext3D::UNSIGNED_BYTE, 0)); + ResourceId id = m_nextId++; + Resource resource(textureId, pool, size, format); + m_resources.add(id, resource); + return id; +} + +CCResourceProvider::ResourceId CCResourceProvider::createBitmap(int pool, const IntSize& size) +{ + ASSERT(CCProxy::isImplThread()); + + uint8_t* pixels = new uint8_t[size.width() * size.height() * 4]; + + ResourceId id = m_nextId++; + Resource resource(pixels, pool, size, GraphicsContext3D::RGBA); + m_resources.add(id, resource); + return id; +} + +CCResourceProvider::ResourceId CCResourceProvider::createResourceFromExternalTexture(unsigned textureId) +{ + ASSERT(CCProxy::isImplThread()); + ASSERT(m_context->context3D()); + ResourceId id = m_nextId++; + Resource resource(textureId, 0, IntSize(), 0); + resource.external = true; + m_resources.add(id, resource); + return id; +} + +void CCResourceProvider::deleteResource(ResourceId id) +{ + ASSERT(CCProxy::isImplThread()); + ResourceMap::iterator it = m_resources.find(id); + ASSERT(it != m_resources.end() && !it->second.lockedForWrite && !it->second.lockForReadCount); + + if (it->second.glId && !it->second.external) { + WebGraphicsContext3D* context3d = m_context->context3D(); + ASSERT(context3d); + GLC(context3d, context3d->deleteTexture(it->second.glId)); + } + if (it->second.pixels) + delete it->second.pixels; + + m_resources.remove(it); +} + +void CCResourceProvider::deleteOwnedResources(int pool) +{ + ASSERT(CCProxy::isImplThread()); + ResourceIdArray toDelete; + for (ResourceMap::iterator it = m_resources.begin(); it != m_resources.end(); ++it) { + if (it->second.pool == pool && !it->second.external) + toDelete.append(it->first); + } + for (ResourceIdArray::iterator it = toDelete.begin(); it != toDelete.end(); ++it) + deleteResource(*it); +} + +CCResourceProvider::ResourceType CCResourceProvider::resourceType(ResourceId id) +{ + ResourceMap::iterator it = m_resources.find(id); + ASSERT(it != m_resources.end()); + return it->second.type; +} + +void CCResourceProvider::upload(ResourceId id, const uint8_t* image, const IntRect& imageRect, const IntRect& sourceRect, const IntSize& destOffset) +{ + ASSERT(CCProxy::isImplThread()); + ResourceMap::iterator it = m_resources.find(id); + ASSERT(it != m_resources.end() && !it->second.lockedForWrite && !it->second.lockForReadCount && !it->second.external); + + if (it->second.glId) { + WebGraphicsContext3D* context3d = m_context->context3D(); + ASSERT(context3d); + ASSERT(m_texSubImage.get()); + context3d->bindTexture(GraphicsContext3D::TEXTURE_2D, it->second.glId); + m_texSubImage->upload(image, imageRect, sourceRect, destOffset, it->second.format, context3d); + } + + if (it->second.pixels) { + SkBitmap srcFull; + srcFull.setConfig(SkBitmap::kARGB_8888_Config, imageRect.width(), imageRect.height()); + srcFull.setPixels(const_cast<uint8_t*>(image)); + SkBitmap srcSubset; + SkIRect skSourceRect = SkIRect::MakeXYWH(sourceRect.x(), sourceRect.y(), sourceRect.width(), sourceRect.height()); + skSourceRect.offset(-imageRect.x(), -imageRect.y()); + srcFull.extractSubset(&srcSubset, skSourceRect); + + ScopedWriteLockSoftware lock(this, id); + SkCanvas* dest = lock.skCanvas(); + dest->writePixels(srcSubset, destOffset.width(), destOffset.height()); + } +} + +void CCResourceProvider::flush() +{ + ASSERT(CCProxy::isImplThread()); + WebGraphicsContext3D* context3d = m_context->context3D(); + if (context3d) + context3d->flush(); +} + +bool CCResourceProvider::shallowFlushIfSupported() +{ + ASSERT(CCProxy::isImplThread()); + WebGraphicsContext3D* context3d = m_context->context3D(); + if (!context3d || !m_useShallowFlush) + return false; + + context3d->shallowFlushCHROMIUM(); + return true; +} + +const CCResourceProvider::Resource* CCResourceProvider::lockForRead(ResourceId id) +{ + ASSERT(CCProxy::isImplThread()); + ResourceMap::iterator it = m_resources.find(id); + ASSERT(it != m_resources.end() && !it->second.lockedForWrite); + it->second.lockForReadCount++; + return &it->second; +} + +void CCResourceProvider::unlockForRead(ResourceId id) +{ + ASSERT(CCProxy::isImplThread()); + ResourceMap::iterator it = m_resources.find(id); + ASSERT(it != m_resources.end() && it->second.lockForReadCount > 0); + it->second.lockForReadCount--; +} + +const CCResourceProvider::Resource* CCResourceProvider::lockForWrite(ResourceId id) +{ + ASSERT(CCProxy::isImplThread()); + ResourceMap::iterator it = m_resources.find(id); + ASSERT(it != m_resources.end() && !it->second.lockedForWrite && !it->second.lockForReadCount && !it->second.external); + it->second.lockedForWrite = true; + return &it->second; +} + +void CCResourceProvider::unlockForWrite(ResourceId id) +{ + ASSERT(CCProxy::isImplThread()); + ResourceMap::iterator it = m_resources.find(id); + ASSERT(it != m_resources.end() && it->second.lockedForWrite && !it->second.external); + it->second.lockedForWrite = false; +} + +CCResourceProvider::ScopedReadLockGL::ScopedReadLockGL(CCResourceProvider* resourceProvider, CCResourceProvider::ResourceId resourceId) + : m_resourceProvider(resourceProvider) + , m_resourceId(resourceId) + , m_textureId(resourceProvider->lockForRead(resourceId)->glId) +{ + ASSERT(m_textureId); +} + +CCResourceProvider::ScopedReadLockGL::~ScopedReadLockGL() +{ + m_resourceProvider->unlockForRead(m_resourceId); +} + +CCResourceProvider::ScopedWriteLockGL::ScopedWriteLockGL(CCResourceProvider* resourceProvider, CCResourceProvider::ResourceId resourceId) + : m_resourceProvider(resourceProvider) + , m_resourceId(resourceId) + , m_textureId(resourceProvider->lockForWrite(resourceId)->glId) +{ + ASSERT(m_textureId); +} + +CCResourceProvider::ScopedWriteLockGL::~ScopedWriteLockGL() +{ + m_resourceProvider->unlockForWrite(m_resourceId); +} + +void CCResourceProvider::populateSkBitmapWithResource(SkBitmap* skBitmap, const Resource* resource) +{ + ASSERT(resource->pixels); + ASSERT(resource->format == GraphicsContext3D::RGBA); + skBitmap->setConfig(SkBitmap::kARGB_8888_Config, resource->size.width(), resource->size.height()); + skBitmap->setPixels(resource->pixels); +} + +CCResourceProvider::ScopedReadLockSoftware::ScopedReadLockSoftware(CCResourceProvider* resourceProvider, CCResourceProvider::ResourceId resourceId) + : m_resourceProvider(resourceProvider) + , m_resourceId(resourceId) +{ + CCResourceProvider::populateSkBitmapWithResource(&m_skBitmap, resourceProvider->lockForRead(resourceId)); +} + +CCResourceProvider::ScopedReadLockSoftware::~ScopedReadLockSoftware() +{ + m_resourceProvider->unlockForRead(m_resourceId); +} + +CCResourceProvider::ScopedWriteLockSoftware::ScopedWriteLockSoftware(CCResourceProvider* resourceProvider, CCResourceProvider::ResourceId resourceId) + : m_resourceProvider(resourceProvider) + , m_resourceId(resourceId) +{ + CCResourceProvider::populateSkBitmapWithResource(&m_skBitmap, resourceProvider->lockForWrite(resourceId)); + m_skCanvas.setBitmapDevice(m_skBitmap); +} + +CCResourceProvider::ScopedWriteLockSoftware::~ScopedWriteLockSoftware() +{ + m_resourceProvider->unlockForWrite(m_resourceId); +} + +CCResourceProvider::CCResourceProvider(CCGraphicsContext* context) + : m_context(context) + , m_nextId(1) + , m_nextChild(1) + , m_defaultResourceType(GLTexture) + , m_useTextureStorageExt(false) + , m_useTextureUsageHint(false) + , m_useShallowFlush(false) + , m_maxTextureSize(0) +{ +} + +bool CCResourceProvider::initialize() +{ + ASSERT(CCProxy::isImplThread()); + WebGraphicsContext3D* context3d = m_context->context3D(); + if (!context3d) { + m_maxTextureSize = INT_MAX; + + // FIXME: Implement this path for software compositing. + return false; + } + if (!context3d->makeContextCurrent()) + return false; + + WebKit::WebString extensionsWebString = context3d->getString(GraphicsContext3D::EXTENSIONS); + String extensionsString(extensionsWebString.data(), extensionsWebString.length()); + Vector<String> extensions; + extensionsString.split(' ', extensions); + bool useMapSub = false; + for (size_t i = 0; i < extensions.size(); ++i) { + if (extensions[i] == "GL_EXT_texture_storage") + m_useTextureStorageExt = true; + else if (extensions[i] == "GL_ANGLE_texture_usage") + m_useTextureUsageHint = true; + else if (extensions[i] == "GL_CHROMIUM_map_sub") + useMapSub = true; + else if (extensions[i] == "GL_CHROMIUM_shallow_flush") + m_useShallowFlush = true; + } + + m_texSubImage = adoptPtr(new LayerTextureSubImage(useMapSub)); + GLC(context3d, context3d->getIntegerv(GraphicsContext3D::MAX_TEXTURE_SIZE, &m_maxTextureSize)); + return true; +} + +int CCResourceProvider::createChild(int pool) +{ + ASSERT(CCProxy::isImplThread()); + Child childInfo; + childInfo.pool = pool; + int child = m_nextChild++; + m_children.add(child, childInfo); + return child; +} + +void CCResourceProvider::destroyChild(int child) +{ + ASSERT(CCProxy::isImplThread()); + ChildMap::iterator it = m_children.find(child); + ASSERT(it != m_children.end()); + deleteOwnedResources(it->second.pool); + m_children.remove(it); + trimMailboxDeque(); +} + +const CCResourceProvider::ResourceIdMap& CCResourceProvider::getChildToParentMap(int child) const +{ + ASSERT(CCProxy::isImplThread()); + ChildMap::const_iterator it = m_children.find(child); + ASSERT(it != m_children.end()); + return it->second.childToParentMap; +} + +CCResourceProvider::TransferableResourceList CCResourceProvider::prepareSendToParent(const ResourceIdArray& resources) +{ + ASSERT(CCProxy::isImplThread()); + TransferableResourceList list; + list.syncPoint = 0; + WebGraphicsContext3D* context3d = m_context->context3D(); + if (!context3d || !context3d->makeContextCurrent()) { + // FIXME: Implement this path for software compositing. + return list; + } + for (ResourceIdArray::const_iterator it = resources.begin(); it != resources.end(); ++it) { + TransferableResource resource; + if (transferResource(context3d, *it, &resource)) { + m_resources.find(*it)->second.exported = true; + list.resources.append(resource); + } + } + if (list.resources.size()) + list.syncPoint = context3d->insertSyncPoint(); + return list; +} + +CCResourceProvider::TransferableResourceList CCResourceProvider::prepareSendToChild(int child, const ResourceIdArray& resources) +{ + ASSERT(CCProxy::isImplThread()); + TransferableResourceList list; + list.syncPoint = 0; + WebGraphicsContext3D* context3d = m_context->context3D(); + if (!context3d || !context3d->makeContextCurrent()) { + // FIXME: Implement this path for software compositing. + return list; + } + Child& childInfo = m_children.find(child)->second; + for (ResourceIdArray::const_iterator it = resources.begin(); it != resources.end(); ++it) { + TransferableResource resource; + if (!transferResource(context3d, *it, &resource)) + ASSERT_NOT_REACHED(); + resource.id = childInfo.parentToChildMap.get(*it); + childInfo.parentToChildMap.remove(*it); + childInfo.childToParentMap.remove(resource.id); + list.resources.append(resource); + deleteResource(*it); + } + if (list.resources.size()) + list.syncPoint = context3d->insertSyncPoint(); + return list; +} + +void CCResourceProvider::receiveFromChild(int child, const TransferableResourceList& resources) +{ + ASSERT(CCProxy::isImplThread()); + WebGraphicsContext3D* context3d = m_context->context3D(); + if (!context3d || !context3d->makeContextCurrent()) { + // FIXME: Implement this path for software compositing. + return; + } + if (resources.syncPoint) { + // NOTE: If the parent is a browser and the child a renderer, the parent + // is not supposed to have its context wait, because that could induce + // deadlocks and/or security issues. The caller is responsible for + // waiting asynchronously, and resetting syncPoint before calling this. + // However if the parent is a renderer (e.g. browser tag), it may be ok + // (and is simpler) to wait. + GLC(context3d, context3d->waitSyncPoint(resources.syncPoint)); + } + Child& childInfo = m_children.find(child)->second; + for (Vector<TransferableResource>::const_iterator it = resources.resources.begin(); it != resources.resources.end(); ++it) { + unsigned textureId; + GLC(context3d, textureId = context3d->createTexture()); + GLC(context3d, context3d->bindTexture(GraphicsContext3D::TEXTURE_2D, textureId)); + GLC(context3d, context3d->consumeTextureCHROMIUM(GraphicsContext3D::TEXTURE_2D, it->mailbox.name)); + ResourceId id = m_nextId++; + Resource resource(textureId, childInfo.pool, it->size, it->format); + m_resources.add(id, resource); + m_mailboxes.append(it->mailbox); + childInfo.parentToChildMap.add(id, it->id); + childInfo.childToParentMap.add(it->id, id); + } +} + +void CCResourceProvider::receiveFromParent(const TransferableResourceList& resources) +{ + ASSERT(CCProxy::isImplThread()); + WebGraphicsContext3D* context3d = m_context->context3D(); + if (!context3d || !context3d->makeContextCurrent()) { + // FIXME: Implement this path for software compositing. + return; + } + if (resources.syncPoint) + GLC(context3d, context3d->waitSyncPoint(resources.syncPoint)); + for (Vector<TransferableResource>::const_iterator it = resources.resources.begin(); it != resources.resources.end(); ++it) { + Resource& resource = m_resources.find(it->id)->second; + ASSERT(resource.exported); + resource.exported = false; + GLC(context3d, context3d->bindTexture(GraphicsContext3D::TEXTURE_2D, resource.glId)); + GLC(context3d, context3d->consumeTextureCHROMIUM(GraphicsContext3D::TEXTURE_2D, it->mailbox.name)); + m_mailboxes.append(it->mailbox); + } +} + +bool CCResourceProvider::transferResource(WebGraphicsContext3D* context, ResourceId id, TransferableResource* resource) +{ + ASSERT(CCProxy::isImplThread()); + ResourceMap::const_iterator it = m_resources.find(id); + ASSERT(it != m_resources.end() && !it->second.lockedForWrite && !it->second.lockForReadCount && !it->second.external); + if (it->second.exported) + return false; + resource->id = id; + resource->format = it->second.format; + resource->size = it->second.size; + if (!m_mailboxes.isEmpty()) + resource->mailbox = m_mailboxes.takeFirst(); + else + GLC(context, context->genMailboxCHROMIUM(resource->mailbox.name)); + GLC(context, context->bindTexture(GraphicsContext3D::TEXTURE_2D, it->second.glId)); + GLC(context, context->produceTextureCHROMIUM(GraphicsContext3D::TEXTURE_2D, resource->mailbox.name)); + return true; +} + +void CCResourceProvider::trimMailboxDeque() +{ + // Trim the mailbox deque to the maximum number of resources we may need to + // send. + // If we have a parent, any non-external resource not already transfered is + // eligible to be sent to the parent. Otherwise, all resources belonging to + // a child might need to be sent back to the child. + size_t maxMailboxCount = 0; + if (m_context->capabilities().hasParentCompositor) { + for (ResourceMap::iterator it = m_resources.begin(); it != m_resources.end(); ++it) { + if (!it->second.exported && !it->second.external) + ++maxMailboxCount; + } + } else { + HashSet<int> childPoolSet; + for (ChildMap::iterator it = m_children.begin(); it != m_children.end(); ++it) + childPoolSet.add(it->second.pool); + for (ResourceMap::iterator it = m_resources.begin(); it != m_resources.end(); ++it) { + if (childPoolSet.contains(it->second.pool)) + ++maxMailboxCount; + } + } + while (m_mailboxes.size() > maxMailboxCount) + m_mailboxes.removeFirst(); +} + +} diff --git a/cc/CCResourceProvider.h b/cc/CCResourceProvider.h new file mode 100644 index 0000000..8368683 --- /dev/null +++ b/cc/CCResourceProvider.h @@ -0,0 +1,289 @@ +// Copyright 2012 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. + + +#ifndef CCResourceProvider_h +#define CCResourceProvider_h + +#include "CCGraphicsContext.h" +#include "GraphicsContext3D.h" +#include "IntSize.h" +#include "SkBitmap.h" +#include "SkCanvas.h" +#include <wtf/Deque.h> +#include <wtf/HashMap.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace WebCore { + +class IntRect; +class LayerTextureSubImage; + +// Thread-safety notes: this class is not thread-safe and can only be called +// from the thread it was created on (in practice, the compositor thread). +class CCResourceProvider { + WTF_MAKE_NONCOPYABLE(CCResourceProvider); +public: + typedef unsigned ResourceId; + typedef Vector<ResourceId> ResourceIdArray; + typedef HashMap<ResourceId, ResourceId> ResourceIdMap; + enum TextureUsageHint { TextureUsageAny, TextureUsageFramebuffer }; + enum ResourceType { + GLTexture = 1, + Bitmap, + }; + struct Mailbox { + GC3Dbyte name[64]; + }; + struct TransferableResource { + unsigned id; + GC3Denum format; + IntSize size; + Mailbox mailbox; + }; + typedef Vector<TransferableResource> TransferableResourceArray; + struct TransferableResourceList { + TransferableResourceArray resources; + unsigned syncPoint; + }; + + static PassOwnPtr<CCResourceProvider> create(CCGraphicsContext*); + + virtual ~CCResourceProvider(); + + WebKit::WebGraphicsContext3D* graphicsContext3D(); + int maxTextureSize() const { return m_maxTextureSize; } + unsigned numResources() const { return m_resources.size(); } + + // Checks whether a resource is in use by a consumer. + bool inUseByConsumer(ResourceId); + + + // Producer interface. + + void setDefaultResourceType(ResourceType type) { m_defaultResourceType = type; } + ResourceType defaultResourceType() const { return m_defaultResourceType; } + ResourceType resourceType(ResourceId); + + // Creates a resource of the default resource type. + ResourceId createResource(int pool, const IntSize&, GC3Denum format, TextureUsageHint); + + // You can also explicitly create a specific resource type. + ResourceId createGLTexture(int pool, const IntSize&, GC3Denum format, TextureUsageHint); + ResourceId createBitmap(int pool, const IntSize&); + // Wraps an external texture into a GL resource. + ResourceId createResourceFromExternalTexture(unsigned textureId); + + void deleteResource(ResourceId); + + // Deletes all resources owned by a given pool. + void deleteOwnedResources(int pool); + + // Upload data from image, copying sourceRect (in image) into destRect (in the resource). + void upload(ResourceId, const uint8_t* image, const IntRect& imageRect, const IntRect& sourceRect, const IntSize& destOffset); + + // Flush all context operations, kicking uploads and ensuring ordering with + // respect to other contexts. + void flush(); + + // Only flush the command buffer if supported. + // Returns true if the shallow flush occurred, false otherwise. + bool shallowFlushIfSupported(); + + // Creates accounting for a child, and associate it with a pool. Resources + // transfered from that child will go to that pool. Returns a child ID. + int createChild(int pool); + + // Destroys accounting for the child, deleting all resources from that pool. + void destroyChild(int child); + + // Gets the child->parent resource ID map. + const ResourceIdMap& getChildToParentMap(int child) const; + + // Prepares resources to be transfered to the parent, moving them to + // mailboxes and serializing meta-data into TransferableResources. + // Resources are not removed from the CCResourceProvider, but are markes as + // "in use". + TransferableResourceList prepareSendToParent(const ResourceIdArray&); + + // Prepares resources to be transfered back to the child, moving them to + // mailboxes and serializing meta-data into TransferableResources. + // Resources are removed from the CCResourceProvider. Note: the resource IDs + // passed are in the parent namespace and will be translated to the child + // namespace when returned. + TransferableResourceList prepareSendToChild(int child, const ResourceIdArray&); + + // Receives resources from a child, moving them from mailboxes. Resource IDs + // passed are in the child namespace, and will be translated to the parent + // namespace, added to the child->parent map. + // NOTE: if the syncPoint filed in TransferableResourceList is set, this + // will wait on it. + void receiveFromChild(int child, const TransferableResourceList&); + + // Receives resources from the parent, moving them from mailboxes. Resource IDs + // passed are in the child namespace. + // NOTE: if the syncPoint filed in TransferableResourceList is set, this + // will wait on it. + void receiveFromParent(const TransferableResourceList&); + + // Only for testing + size_t mailboxCount() const { return m_mailboxes.size(); } + + // The following lock classes are part of the CCResourceProvider API and are + // needed to read and write the resource contents. The user must ensure + // that they only use GL locks on GL resources, etc, and this is enforced + // by assertions. + class ScopedReadLockGL { + WTF_MAKE_NONCOPYABLE(ScopedReadLockGL); + public: + ScopedReadLockGL(CCResourceProvider*, CCResourceProvider::ResourceId); + ~ScopedReadLockGL(); + + unsigned textureId() const { return m_textureId; } + + private: + CCResourceProvider* m_resourceProvider; + CCResourceProvider::ResourceId m_resourceId; + unsigned m_textureId; + }; + + class ScopedWriteLockGL { + WTF_MAKE_NONCOPYABLE(ScopedWriteLockGL); + public: + ScopedWriteLockGL(CCResourceProvider*, CCResourceProvider::ResourceId); + ~ScopedWriteLockGL(); + + unsigned textureId() const { return m_textureId; } + + private: + CCResourceProvider* m_resourceProvider; + CCResourceProvider::ResourceId m_resourceId; + unsigned m_textureId; + }; + + class ScopedReadLockSoftware { + WTF_MAKE_NONCOPYABLE(ScopedReadLockSoftware); + public: + ScopedReadLockSoftware(CCResourceProvider*, CCResourceProvider::ResourceId); + ~ScopedReadLockSoftware(); + + const SkBitmap* skBitmap() const { return &m_skBitmap; } + + private: + CCResourceProvider* m_resourceProvider; + CCResourceProvider::ResourceId m_resourceId; + SkBitmap m_skBitmap; + }; + + class ScopedWriteLockSoftware { + WTF_MAKE_NONCOPYABLE(ScopedWriteLockSoftware); + public: + ScopedWriteLockSoftware(CCResourceProvider*, CCResourceProvider::ResourceId); + ~ScopedWriteLockSoftware(); + + SkCanvas* skCanvas() { return &m_skCanvas; } + + private: + CCResourceProvider* m_resourceProvider; + CCResourceProvider::ResourceId m_resourceId; + SkBitmap m_skBitmap; + SkCanvas m_skCanvas; + }; + +private: + struct Resource { + Resource() + : glId(0) + , pixels(0) + , pool(0) + , lockForReadCount(0) + , lockedForWrite(false) + , external(false) + , exported(false) + , size() + , format(0) + , type(static_cast<ResourceType>(0)) + { } + Resource(unsigned textureId, int pool, const IntSize& size, GC3Denum format) + : glId(textureId) + , pixels(0) + , pool(pool) + , lockForReadCount(0) + , lockedForWrite(false) + , external(false) + , exported(false) + , size(size) + , format(format) + , type(GLTexture) + { } + Resource(uint8_t* pixels, int pool, const IntSize& size, GC3Denum format) + : glId(0) + , pixels(pixels) + , pool(pool) + , lockForReadCount(0) + , lockedForWrite(false) + , external(false) + , exported(false) + , size(size) + , format(format) + , type(Bitmap) + { } + unsigned glId; + uint8_t* pixels; + int pool; + int lockForReadCount; + bool lockedForWrite; + bool external; + bool exported; + IntSize size; + GC3Denum format; + ResourceType type; + }; + typedef HashMap<ResourceId, Resource> ResourceMap; + struct Child { + int pool; + ResourceIdMap childToParentMap; + ResourceIdMap parentToChildMap; + }; + typedef HashMap<int, Child> ChildMap; + + explicit CCResourceProvider(CCGraphicsContext*); + bool initialize(); + + const Resource* lockForRead(ResourceId); + void unlockForRead(ResourceId); + const Resource* lockForWrite(ResourceId); + void unlockForWrite(ResourceId); + static void populateSkBitmapWithResource(SkBitmap*, const Resource*); + + bool transferResource(WebKit::WebGraphicsContext3D*, ResourceId, TransferableResource*); + void trimMailboxDeque(); + + CCGraphicsContext* m_context; + ResourceId m_nextId; + ResourceMap m_resources; + int m_nextChild; + ChildMap m_children; + + Deque<Mailbox> m_mailboxes; + + ResourceType m_defaultResourceType; + bool m_useTextureStorageExt; + bool m_useTextureUsageHint; + bool m_useShallowFlush; + OwnPtr<LayerTextureSubImage> m_texSubImage; + int m_maxTextureSize; +}; + +} + +#endif diff --git a/cc/CCResourceProviderTest.cpp b/cc/CCResourceProviderTest.cpp new file mode 100644 index 0000000..b8fe586 --- /dev/null +++ b/cc/CCResourceProviderTest.cpp @@ -0,0 +1,536 @@ +// Copyright 2012 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 "CCResourceProvider.h" + +#include "CCGraphicsContext.h" +#include "CCSingleThreadProxy.h" // For DebugScopedSetImplThread +#include "CompositorFakeWebGraphicsContext3D.h" +#include "Extensions3DChromium.h" +#include "FakeWebCompositorOutputSurface.h" +#include <gtest/gtest.h> +#include <public/WebGraphicsContext3D.h> +#include <wtf/HashMap.h> +#include <wtf/OwnArrayPtr.h> +#include <wtf/OwnPtr.h> + +using namespace WebCore; +using namespace WebKit; + +namespace { + +size_t textureSize(const IntSize& size, WGC3Denum format) +{ + unsigned int componentsPerPixel = 4; + unsigned int bytesPerComponent = 1; + GraphicsContext3D::computeFormatAndTypeParameters(format, GraphicsContext3D::UNSIGNED_BYTE, &componentsPerPixel, &bytesPerComponent); + return size.width() * size.height() * componentsPerPixel * bytesPerComponent; +} + +struct Texture { + Texture(const IntSize& size, WGC3Denum format) + : size(size) + , format(format) + , data(adoptArrayPtr(new uint8_t[textureSize(size, format)])) + { + } + + IntSize size; + WGC3Denum format; + OwnArrayPtr<uint8_t> data; +}; + +// Shared data between multiple ResourceProviderContext. This contains mailbox +// contents as well as information about sync points. +class ContextSharedData { +public: + static PassOwnPtr<ContextSharedData> create() { return adoptPtr(new ContextSharedData()); } + + unsigned insertSyncPoint() { return m_nextSyncPoint++; } + + void genMailbox(WGC3Dbyte* mailbox) + { + memset(mailbox, 0, sizeof(WGC3Dbyte[64])); + memcpy(mailbox, &m_nextMailBox, sizeof(m_nextMailBox)); + ++m_nextMailBox; + } + + void produceTexture(const WGC3Dbyte* mailboxName, unsigned syncPoint, PassOwnPtr<Texture> texture) + { + unsigned mailbox = 0; + memcpy(&mailbox, mailboxName, sizeof(mailbox)); + ASSERT(mailbox && mailbox < m_nextMailBox); + m_textures.set(mailbox, texture); + ASSERT(m_syncPointForMailbox.get(mailbox) < syncPoint); + m_syncPointForMailbox.set(mailbox, syncPoint); + } + + PassOwnPtr<Texture> consumeTexture(const WGC3Dbyte* mailboxName, unsigned syncPoint) + { + unsigned mailbox = 0; + memcpy(&mailbox, mailboxName, sizeof(mailbox)); + ASSERT(mailbox && mailbox < m_nextMailBox); + + // If the latest sync point the context has waited on is before the sync + // point for when the mailbox was set, pretend we never saw that + // produceTexture. + if (m_syncPointForMailbox.get(mailbox) < syncPoint) + return nullptr; + return m_textures.take(mailbox); + } + +private: + ContextSharedData() + : m_nextSyncPoint(1) + , m_nextMailBox(1) + { } + + unsigned m_nextSyncPoint; + unsigned m_nextMailBox; + typedef HashMap<unsigned, OwnPtr<Texture> > TextureMap; + TextureMap m_textures; + HashMap<unsigned, unsigned> m_syncPointForMailbox; +}; + +class ResourceProviderContext : public CompositorFakeWebGraphicsContext3D { +public: + static PassOwnPtr<ResourceProviderContext> create(ContextSharedData* sharedData) { return adoptPtr(new ResourceProviderContext(Attributes(), sharedData)); } + + virtual unsigned insertSyncPoint() + { + unsigned syncPoint = m_sharedData->insertSyncPoint(); + // Commit the produceTextureCHROMIUM calls at this point, so that + // they're associated with the sync point. + for (PendingProduceTextureList::iterator it = m_pendingProduceTextures.begin(); it != m_pendingProduceTextures.end(); ++it) + m_sharedData->produceTexture((*it)->mailbox, syncPoint, (*it)->texture.release()); + m_pendingProduceTextures.clear(); + return syncPoint; + } + + virtual void waitSyncPoint(unsigned syncPoint) + { + m_lastWaitedSyncPoint = std::max(syncPoint, m_lastWaitedSyncPoint); + } + + virtual void bindTexture(WGC3Denum target, WebGLId texture) + { + ASSERT(target == GraphicsContext3D::TEXTURE_2D); + ASSERT(!texture || m_textures.find(texture) != m_textures.end()); + m_currentTexture = texture; + } + + virtual WebGLId createTexture() + { + WebGLId id = CompositorFakeWebGraphicsContext3D::createTexture(); + m_textures.add(id, nullptr); + return id; + } + + virtual void deleteTexture(WebGLId id) + { + TextureMap::iterator it = m_textures.find(id); + ASSERT(it != m_textures.end()); + m_textures.remove(it); + if (m_currentTexture == id) + m_currentTexture = 0; + } + + virtual void texStorage2DEXT(WGC3Denum target, WGC3Dint levels, WGC3Duint internalformat, + WGC3Dint width, WGC3Dint height) + { + ASSERT(m_currentTexture); + ASSERT(target == GraphicsContext3D::TEXTURE_2D); + ASSERT(levels == 1); + WGC3Denum format = GraphicsContext3D::RGBA; + switch (internalformat) { + case Extensions3D::RGBA8_OES: + break; + case Extensions3DChromium::BGRA8_EXT: + format = Extensions3D::BGRA_EXT; + break; + default: + ASSERT_NOT_REACHED(); + } + allocateTexture(IntSize(width, height), format); + } + + virtual void texImage2D(WGC3Denum target, WGC3Dint level, WGC3Denum internalformat, WGC3Dsizei width, WGC3Dsizei height, WGC3Dint border, WGC3Denum format, WGC3Denum type, const void* pixels) + { + ASSERT(m_currentTexture); + ASSERT(target == GraphicsContext3D::TEXTURE_2D); + ASSERT(!level); + ASSERT(internalformat == format); + ASSERT(!border); + ASSERT(type == GraphicsContext3D::UNSIGNED_BYTE); + allocateTexture(IntSize(width, height), format); + if (pixels) + setPixels(0, 0, width, height, pixels); + } + + virtual void texSubImage2D(WGC3Denum target, WGC3Dint level, WGC3Dint xoffset, WGC3Dint yoffset, WGC3Dsizei width, WGC3Dsizei height, WGC3Denum format, WGC3Denum type, const void* pixels) + { + ASSERT(m_currentTexture); + ASSERT(target == GraphicsContext3D::TEXTURE_2D); + ASSERT(!level); + ASSERT(m_textures.get(m_currentTexture)); + ASSERT(m_textures.get(m_currentTexture)->format == format); + ASSERT(type == GraphicsContext3D::UNSIGNED_BYTE); + ASSERT(pixels); + setPixels(xoffset, yoffset, width, height, pixels); + } + + virtual void genMailboxCHROMIUM(WGC3Dbyte* mailbox) { return m_sharedData->genMailbox(mailbox); } + virtual void produceTextureCHROMIUM(WGC3Denum target, const WGC3Dbyte* mailbox) + { + ASSERT(m_currentTexture); + ASSERT(target == GraphicsContext3D::TEXTURE_2D); + + // Delay movind the texture into the mailbox until the next + // insertSyncPoint, so that it is not visible to other contexts that + // haven't waited on that sync point. + OwnPtr<PendingProduceTexture> pending(adoptPtr(new PendingProduceTexture)); + memcpy(pending->mailbox, mailbox, sizeof(pending->mailbox)); + pending->texture = m_textures.take(m_currentTexture); + m_textures.set(m_currentTexture, nullptr); + m_pendingProduceTextures.append(pending.release()); + } + + virtual void consumeTextureCHROMIUM(WGC3Denum target, const WGC3Dbyte* mailbox) + { + ASSERT(m_currentTexture); + ASSERT(target == GraphicsContext3D::TEXTURE_2D); + m_textures.set(m_currentTexture, m_sharedData->consumeTexture(mailbox, m_lastWaitedSyncPoint)); + } + + void getPixels(const IntSize& size, WGC3Denum format, uint8_t* pixels) + { + ASSERT(m_currentTexture); + Texture* texture = m_textures.get(m_currentTexture); + ASSERT(texture); + ASSERT(texture->size == size); + ASSERT(texture->format == format); + memcpy(pixels, texture->data.get(), textureSize(size, format)); + } + + int textureCount() + { + return m_textures.size(); + } + +protected: + ResourceProviderContext(const Attributes& attrs, ContextSharedData* sharedData) + : CompositorFakeWebGraphicsContext3D(attrs) + , m_sharedData(sharedData) + , m_currentTexture(0) + , m_lastWaitedSyncPoint(0) + { } + +private: + void allocateTexture(const IntSize& size, WGC3Denum format) + { + ASSERT(m_currentTexture); + m_textures.set(m_currentTexture, adoptPtr(new Texture(size, format))); + } + + void setPixels(int xoffset, int yoffset, int width, int height, const void* pixels) + { + ASSERT(m_currentTexture); + Texture* texture = m_textures.get(m_currentTexture); + ASSERT(texture); + ASSERT(xoffset >= 0 && xoffset+width <= texture->size.width()); + ASSERT(yoffset >= 0 && yoffset+height <= texture->size.height()); + ASSERT(pixels); + size_t inPitch = textureSize(IntSize(width, 1), texture->format); + size_t outPitch = textureSize(IntSize(texture->size.width(), 1), texture->format); + uint8_t* dest = texture->data.get() + yoffset * outPitch + textureSize(IntSize(xoffset, 1), texture->format); + const uint8_t* src = static_cast<const uint8_t*>(pixels); + for (int i = 0; i < height; ++i) { + memcpy(dest, src, inPitch); + dest += outPitch; + src += inPitch; + } + } + + typedef HashMap<WebGLId, OwnPtr<Texture> > TextureMap; + struct PendingProduceTexture { + WGC3Dbyte mailbox[64]; + OwnPtr<Texture> texture; + }; + typedef Deque<OwnPtr<PendingProduceTexture> > PendingProduceTextureList; + ContextSharedData* m_sharedData; + WebGLId m_currentTexture; + TextureMap m_textures; + unsigned m_lastWaitedSyncPoint; + PendingProduceTextureList m_pendingProduceTextures; +}; + +class CCResourceProviderTest : public testing::TestWithParam<CCResourceProvider::ResourceType> { +public: + CCResourceProviderTest() + : m_sharedData(ContextSharedData::create()) + , m_context(FakeWebCompositorOutputSurface::create(ResourceProviderContext::create(m_sharedData.get()))) + , m_resourceProvider(CCResourceProvider::create(m_context.get())) + { + m_resourceProvider->setDefaultResourceType(GetParam()); + } + + ResourceProviderContext* context() { return static_cast<ResourceProviderContext*>(m_context->context3D()); } + + void getResourcePixels(CCResourceProvider::ResourceId id, const IntSize& size, WGC3Denum format, uint8_t* pixels) + { + if (GetParam() == CCResourceProvider::GLTexture) { + CCResourceProvider::ScopedReadLockGL lockGL(m_resourceProvider.get(), id); + ASSERT_NE(0U, lockGL.textureId()); + context()->bindTexture(GraphicsContext3D::TEXTURE_2D, lockGL.textureId()); + context()->getPixels(size, format, pixels); + } else if (GetParam() == CCResourceProvider::Bitmap) { + CCResourceProvider::ScopedReadLockSoftware lockSoftware(m_resourceProvider.get(), id); + memcpy(pixels, lockSoftware.skBitmap()->getPixels(), lockSoftware.skBitmap()->getSize()); + } + } + + void expectNumResources(int count) + { + EXPECT_EQ(count, static_cast<int>(m_resourceProvider->numResources())); + if (GetParam() == CCResourceProvider::GLTexture) + EXPECT_EQ(count, context()->textureCount()); + } + +protected: + DebugScopedSetImplThread implThread; + OwnPtr<ContextSharedData> m_sharedData; + OwnPtr<CCGraphicsContext> m_context; + OwnPtr<CCResourceProvider> m_resourceProvider; +}; + +TEST_P(CCResourceProviderTest, Basic) +{ + IntSize size(1, 1); + WGC3Denum format = GraphicsContext3D::RGBA; + int pool = 1; + size_t pixelSize = textureSize(size, format); + ASSERT_EQ(4U, pixelSize); + + CCResourceProvider::ResourceId id = m_resourceProvider->createResource(pool, size, format, CCResourceProvider::TextureUsageAny); + expectNumResources(1); + + uint8_t data[4] = {1, 2, 3, 4}; + IntRect rect(IntPoint(), size); + m_resourceProvider->upload(id, data, rect, rect, IntSize()); + + uint8_t result[4] = {0}; + getResourcePixels(id, size, format, result); + EXPECT_EQ(0, memcmp(data, result, pixelSize)); + + m_resourceProvider->deleteResource(id); + expectNumResources(0); +} + +TEST_P(CCResourceProviderTest, DeleteOwnedResources) +{ + IntSize size(1, 1); + WGC3Denum format = GraphicsContext3D::RGBA; + int pool = 1; + + const int count = 3; + for (int i = 0; i < count; ++i) + m_resourceProvider->createResource(pool, size, format, CCResourceProvider::TextureUsageAny); + expectNumResources(3); + + m_resourceProvider->deleteOwnedResources(pool+1); + expectNumResources(3); + + m_resourceProvider->deleteOwnedResources(pool); + expectNumResources(0); +} + +TEST_P(CCResourceProviderTest, Upload) +{ + IntSize size(2, 2); + WGC3Denum format = GraphicsContext3D::RGBA; + int pool = 1; + size_t pixelSize = textureSize(size, format); + ASSERT_EQ(16U, pixelSize); + + CCResourceProvider::ResourceId id = m_resourceProvider->createResource(pool, size, format, CCResourceProvider::TextureUsageAny); + + uint8_t image[16] = {0}; + IntRect imageRect(IntPoint(), size); + m_resourceProvider->upload(id, image, imageRect, imageRect, IntSize()); + + for (uint8_t i = 0 ; i < pixelSize; ++i) + image[i] = i; + + uint8_t result[16] = {0}; + { + IntRect sourceRect(0, 0, 1, 1); + IntSize destOffset(0, 0); + m_resourceProvider->upload(id, image, imageRect, sourceRect, destOffset); + + uint8_t expected[16] = {0, 1, 2, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + getResourcePixels(id, size, format, result); + EXPECT_EQ(0, memcmp(expected, result, pixelSize)); + } + { + IntRect sourceRect(0, 0, 1, 1); + IntSize destOffset(1, 1); + m_resourceProvider->upload(id, image, imageRect, sourceRect, destOffset); + + uint8_t expected[16] = {0, 1, 2, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3}; + getResourcePixels(id, size, format, result); + EXPECT_EQ(0, memcmp(expected, result, pixelSize)); + } + { + IntRect sourceRect(1, 0, 1, 1); + IntSize destOffset(0, 1); + m_resourceProvider->upload(id, image, imageRect, sourceRect, destOffset); + + uint8_t expected[16] = {0, 1, 2, 3, 0, 0, 0, 0, + 4, 5, 6, 7, 0, 1, 2, 3}; + getResourcePixels(id, size, format, result); + EXPECT_EQ(0, memcmp(expected, result, pixelSize)); + } + { + IntRect offsetImageRect(IntPoint(100, 100), size); + IntRect sourceRect(100, 100, 1, 1); + IntSize destOffset(1, 0); + m_resourceProvider->upload(id, image, offsetImageRect, sourceRect, destOffset); + + uint8_t expected[16] = {0, 1, 2, 3, 0, 1, 2, 3, + 4, 5, 6, 7, 0, 1, 2, 3}; + getResourcePixels(id, size, format, result); + EXPECT_EQ(0, memcmp(expected, result, pixelSize)); + } + + + m_resourceProvider->deleteResource(id); +} + +TEST_P(CCResourceProviderTest, TransferResources) +{ + // Resource transfer is only supported with GL textures for now. + if (GetParam() != CCResourceProvider::GLTexture) + return; + + OwnPtr<CCGraphicsContext> childContext(FakeWebCompositorOutputSurface::create(ResourceProviderContext::create(m_sharedData.get()))); + OwnPtr<CCResourceProvider> childResourceProvider(CCResourceProvider::create(childContext.get())); + + IntSize size(1, 1); + WGC3Denum format = GraphicsContext3D::RGBA; + int pool = 1; + size_t pixelSize = textureSize(size, format); + ASSERT_EQ(4U, pixelSize); + + CCResourceProvider::ResourceId id1 = childResourceProvider->createResource(pool, size, format, CCResourceProvider::TextureUsageAny); + uint8_t data1[4] = {1, 2, 3, 4}; + IntRect rect(IntPoint(), size); + childResourceProvider->upload(id1, data1, rect, rect, IntSize()); + + CCResourceProvider::ResourceId id2 = childResourceProvider->createResource(pool, size, format, CCResourceProvider::TextureUsageAny); + uint8_t data2[4] = {5, 5, 5, 5}; + childResourceProvider->upload(id2, data2, rect, rect, IntSize()); + + int childPool = 2; + int childId = m_resourceProvider->createChild(childPool); + + { + // Transfer some resources to the parent. + CCResourceProvider::ResourceIdArray resourceIdsToTransfer; + resourceIdsToTransfer.append(id1); + resourceIdsToTransfer.append(id2); + CCResourceProvider::TransferableResourceList list = childResourceProvider->prepareSendToParent(resourceIdsToTransfer); + EXPECT_NE(0u, list.syncPoint); + EXPECT_EQ(2u, list.resources.size()); + EXPECT_TRUE(childResourceProvider->inUseByConsumer(id1)); + EXPECT_TRUE(childResourceProvider->inUseByConsumer(id2)); + m_resourceProvider->receiveFromChild(childId, list); + } + + EXPECT_EQ(2u, m_resourceProvider->numResources()); + EXPECT_EQ(2u, m_resourceProvider->mailboxCount()); + CCResourceProvider::ResourceIdMap resourceMap = m_resourceProvider->getChildToParentMap(childId); + CCResourceProvider::ResourceId mappedId1 = resourceMap.get(id1); + CCResourceProvider::ResourceId mappedId2 = resourceMap.get(id2); + EXPECT_NE(0u, mappedId1); + EXPECT_NE(0u, mappedId2); + EXPECT_FALSE(m_resourceProvider->inUseByConsumer(id1)); + EXPECT_FALSE(m_resourceProvider->inUseByConsumer(id2)); + + uint8_t result[4] = {0}; + getResourcePixels(mappedId1, size, format, result); + EXPECT_EQ(0, memcmp(data1, result, pixelSize)); + + getResourcePixels(mappedId2, size, format, result); + EXPECT_EQ(0, memcmp(data2, result, pixelSize)); + + { + // Check that transfering again the same resource from the child to the + // parent is a noop. + CCResourceProvider::ResourceIdArray resourceIdsToTransfer; + resourceIdsToTransfer.append(id1); + CCResourceProvider::TransferableResourceList list = childResourceProvider->prepareSendToParent(resourceIdsToTransfer); + EXPECT_EQ(0u, list.syncPoint); + EXPECT_EQ(0u, list.resources.size()); + } + + { + // Transfer resources back from the parent to the child. + CCResourceProvider::ResourceIdArray resourceIdsToTransfer; + resourceIdsToTransfer.append(mappedId1); + resourceIdsToTransfer.append(mappedId2); + CCResourceProvider::TransferableResourceList list = m_resourceProvider->prepareSendToChild(childId, resourceIdsToTransfer); + EXPECT_NE(0u, list.syncPoint); + EXPECT_EQ(2u, list.resources.size()); + childResourceProvider->receiveFromParent(list); + } + EXPECT_EQ(0u, m_resourceProvider->mailboxCount()); + EXPECT_EQ(2u, childResourceProvider->mailboxCount()); + EXPECT_FALSE(childResourceProvider->inUseByConsumer(id1)); + EXPECT_FALSE(childResourceProvider->inUseByConsumer(id2)); + + ResourceProviderContext* childContext3D = static_cast<ResourceProviderContext*>(childContext->context3D()); + { + CCResourceProvider::ScopedReadLockGL lock(childResourceProvider.get(), id1); + ASSERT_NE(0U, lock.textureId()); + childContext3D->bindTexture(GraphicsContext3D::TEXTURE_2D, lock.textureId()); + childContext3D->getPixels(size, format, result); + EXPECT_EQ(0, memcmp(data1, result, pixelSize)); + } + { + CCResourceProvider::ScopedReadLockGL lock(childResourceProvider.get(), id2); + ASSERT_NE(0U, lock.textureId()); + childContext3D->bindTexture(GraphicsContext3D::TEXTURE_2D, lock.textureId()); + childContext3D->getPixels(size, format, result); + EXPECT_EQ(0, memcmp(data2, result, pixelSize)); + } + + { + // Transfer resources to the parent again. + CCResourceProvider::ResourceIdArray resourceIdsToTransfer; + resourceIdsToTransfer.append(id1); + resourceIdsToTransfer.append(id2); + CCResourceProvider::TransferableResourceList list = childResourceProvider->prepareSendToParent(resourceIdsToTransfer); + EXPECT_NE(0u, list.syncPoint); + EXPECT_EQ(2u, list.resources.size()); + EXPECT_TRUE(childResourceProvider->inUseByConsumer(id1)); + EXPECT_TRUE(childResourceProvider->inUseByConsumer(id2)); + m_resourceProvider->receiveFromChild(childId, list); + } + + EXPECT_EQ(2u, m_resourceProvider->numResources()); + m_resourceProvider->destroyChild(childId); + EXPECT_EQ(0u, m_resourceProvider->numResources()); + EXPECT_EQ(0u, m_resourceProvider->mailboxCount()); +} + +INSTANTIATE_TEST_CASE_P(CCResourceProviderTests, + CCResourceProviderTest, + ::testing::Values(CCResourceProvider::GLTexture, + CCResourceProvider::Bitmap)); + +} // namespace diff --git a/cc/CCScheduler.cpp b/cc/CCScheduler.cpp new file mode 100644 index 0000000..1840b4a --- /dev/null +++ b/cc/CCScheduler.cpp @@ -0,0 +1,191 @@ +// 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 "config.h" + +#include "CCScheduler.h" + +#include "TraceEvent.h" + +namespace WebCore { + +CCScheduler::CCScheduler(CCSchedulerClient* client, PassOwnPtr<CCFrameRateController> frameRateController) + : m_client(client) + , m_frameRateController(frameRateController) + , m_updateMoreResourcesPending(false) +{ + ASSERT(m_client); + m_frameRateController->setClient(this); + m_frameRateController->setActive(m_stateMachine.vsyncCallbackNeeded()); +} + +CCScheduler::~CCScheduler() +{ + m_frameRateController->setActive(false); +} + +void CCScheduler::setCanBeginFrame(bool can) +{ + m_stateMachine.setCanBeginFrame(can); + processScheduledActions(); +} + +void CCScheduler::setVisible(bool visible) +{ + m_stateMachine.setVisible(visible); + processScheduledActions(); +} + +void CCScheduler::setNeedsCommit() +{ + m_stateMachine.setNeedsCommit(); + processScheduledActions(); +} + +void CCScheduler::setNeedsForcedCommit() +{ + m_stateMachine.setNeedsForcedCommit(); + processScheduledActions(); +} + +void CCScheduler::setNeedsRedraw() +{ + m_stateMachine.setNeedsRedraw(); + processScheduledActions(); +} + +void CCScheduler::setNeedsForcedRedraw() +{ + m_stateMachine.setNeedsForcedRedraw(); + processScheduledActions(); +} + +void CCScheduler::setMainThreadNeedsLayerTextures() +{ + m_stateMachine.setMainThreadNeedsLayerTextures(); + processScheduledActions(); +} + +void CCScheduler::beginFrameComplete() +{ + TRACE_EVENT0("cc", "CCScheduler::beginFrameComplete"); + m_stateMachine.beginFrameComplete(); + processScheduledActions(); +} + +void CCScheduler::beginFrameAborted() +{ + TRACE_EVENT0("cc", "CCScheduler::beginFrameAborted"); + m_stateMachine.beginFrameAborted(); + processScheduledActions(); +} + +void CCScheduler::setMaxFramesPending(int maxFramesPending) +{ + m_frameRateController->setMaxFramesPending(maxFramesPending); +} + +void CCScheduler::didSwapBuffersComplete() +{ + TRACE_EVENT0("cc", "CCScheduler::didSwapBuffersComplete"); + m_frameRateController->didFinishFrame(); +} + +void CCScheduler::didLoseContext() +{ + TRACE_EVENT0("cc", "CCScheduler::didLoseContext"); + m_frameRateController->didAbortAllPendingFrames(); + m_stateMachine.didLoseContext(); + processScheduledActions(); +} + +void CCScheduler::didRecreateContext() +{ + TRACE_EVENT0("cc", "CCScheduler::didRecreateContext"); + m_stateMachine.didRecreateContext(); + processScheduledActions(); +} + +void CCScheduler::setTimebaseAndInterval(double timebase, double intervalSeconds) +{ + m_frameRateController->setTimebaseAndInterval(timebase, intervalSeconds); +} + +void CCScheduler::vsyncTick() +{ + if (m_updateMoreResourcesPending) { + m_updateMoreResourcesPending = false; + m_stateMachine.beginUpdateMoreResourcesComplete(m_client->hasMoreResourceUpdates()); + } + TRACE_EVENT0("cc", "CCScheduler::vsyncTick"); + + m_stateMachine.didEnterVSync(); + processScheduledActions(); + m_stateMachine.didLeaveVSync(); +} + +CCSchedulerStateMachine::Action CCScheduler::nextAction() +{ + m_stateMachine.setCanDraw(m_client->canDraw()); + return m_stateMachine.nextAction(); +} + +void CCScheduler::processScheduledActions() +{ + // Early out so we don't spam TRACE_EVENTS with useless processScheduledActions. + if (nextAction() == CCSchedulerStateMachine::ACTION_NONE) { + m_frameRateController->setActive(m_stateMachine.vsyncCallbackNeeded()); + return; + } + + // This function can re-enter itself. For example, draw may call + // setNeedsCommit. Proceeed with caution. + CCSchedulerStateMachine::Action action; + do { + action = nextAction(); + m_stateMachine.updateState(action); + TRACE_EVENT1("cc", "CCScheduler::processScheduledActions()", "action", action); + + switch (action) { + case CCSchedulerStateMachine::ACTION_NONE: + break; + case CCSchedulerStateMachine::ACTION_BEGIN_FRAME: + m_client->scheduledActionBeginFrame(); + break; + case CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES: + if (m_client->hasMoreResourceUpdates()) { + m_client->scheduledActionUpdateMoreResources(m_frameRateController->nextTickTimeIfActivated()); + m_updateMoreResourcesPending = true; + } else + m_stateMachine.beginUpdateMoreResourcesComplete(false); + break; + case CCSchedulerStateMachine::ACTION_COMMIT: + m_client->scheduledActionCommit(); + break; + case CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE: { + CCScheduledActionDrawAndSwapResult result = m_client->scheduledActionDrawAndSwapIfPossible(); + m_stateMachine.didDrawIfPossibleCompleted(result.didDraw); + if (result.didSwap) + m_frameRateController->didBeginFrame(); + break; + } + case CCSchedulerStateMachine::ACTION_DRAW_FORCED: { + CCScheduledActionDrawAndSwapResult result = m_client->scheduledActionDrawAndSwapForced(); + if (result.didSwap) + m_frameRateController->didBeginFrame(); + break; + } case CCSchedulerStateMachine::ACTION_BEGIN_CONTEXT_RECREATION: + m_client->scheduledActionBeginContextRecreation(); + break; + case CCSchedulerStateMachine::ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD: + m_client->scheduledActionAcquireLayerTexturesForMainThread(); + break; + } + } while (action != CCSchedulerStateMachine::ACTION_NONE); + + // Activate or deactivate the frame rate controller. + m_frameRateController->setActive(m_stateMachine.vsyncCallbackNeeded()); +} + +} diff --git a/cc/CCScheduler.h b/cc/CCScheduler.h new file mode 100644 index 0000000..10fce73 --- /dev/null +++ b/cc/CCScheduler.h @@ -0,0 +1,107 @@ +// 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. + +#ifndef CCScheduler_h +#define CCScheduler_h + +#include "CCFrameRateController.h" +#include "CCSchedulerStateMachine.h" + +#include <wtf/Noncopyable.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCThread; + +struct CCScheduledActionDrawAndSwapResult { + CCScheduledActionDrawAndSwapResult() + : didDraw(false) + , didSwap(false) + { + } + CCScheduledActionDrawAndSwapResult(bool didDraw, bool didSwap) + : didDraw(didDraw) + , didSwap(didSwap) + { + } + bool didDraw; + bool didSwap; +}; + +class CCSchedulerClient { +public: + virtual bool canDraw() = 0; + virtual bool hasMoreResourceUpdates() const = 0; + + virtual void scheduledActionBeginFrame() = 0; + virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapIfPossible() = 0; + virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapForced() = 0; + virtual void scheduledActionUpdateMoreResources(double monotonicTimeLimit) = 0; + virtual void scheduledActionCommit() = 0; + virtual void scheduledActionBeginContextRecreation() = 0; + virtual void scheduledActionAcquireLayerTexturesForMainThread() = 0; + +protected: + virtual ~CCSchedulerClient() { } +}; + +class CCScheduler : CCFrameRateControllerClient { + WTF_MAKE_NONCOPYABLE(CCScheduler); +public: + static PassOwnPtr<CCScheduler> create(CCSchedulerClient* client, PassOwnPtr<CCFrameRateController> frameRateController) + { + return adoptPtr(new CCScheduler(client, frameRateController)); + } + + virtual ~CCScheduler(); + + void setCanBeginFrame(bool); + + void setVisible(bool); + + void setNeedsCommit(); + + // Like setNeedsCommit(), but ensures a commit will definitely happen even if we are not visible. + void setNeedsForcedCommit(); + + void setNeedsRedraw(); + + void setMainThreadNeedsLayerTextures(); + + // Like setNeedsRedraw(), but ensures the draw will definitely happen even if we are not visible. + void setNeedsForcedRedraw(); + + void beginFrameComplete(); + void beginFrameAborted(); + + void setMaxFramesPending(int); + void didSwapBuffersComplete(); + + void didLoseContext(); + void didRecreateContext(); + + bool commitPending() const { return m_stateMachine.commitPending(); } + bool redrawPending() const { return m_stateMachine.redrawPending(); } + + void setTimebaseAndInterval(double timebase, double intervalSeconds); + + // CCFrameRateControllerClient implementation + virtual void vsyncTick() OVERRIDE; + +private: + CCScheduler(CCSchedulerClient*, PassOwnPtr<CCFrameRateController>); + + CCSchedulerStateMachine::Action nextAction(); + void processScheduledActions(); + + CCSchedulerClient* m_client; + OwnPtr<CCFrameRateController> m_frameRateController; + CCSchedulerStateMachine m_stateMachine; + bool m_updateMoreResourcesPending; +}; + +} + +#endif // CCScheduler_h diff --git a/cc/CCSchedulerStateMachine.cpp b/cc/CCSchedulerStateMachine.cpp new file mode 100644 index 0000000..eda92ae --- /dev/null +++ b/cc/CCSchedulerStateMachine.cpp @@ -0,0 +1,316 @@ +// 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 "config.h" + +#include "CCSchedulerStateMachine.h" + +namespace WebCore { + +CCSchedulerStateMachine::CCSchedulerStateMachine() + : m_commitState(COMMIT_STATE_IDLE) + , m_currentFrameNumber(0) + , m_lastFrameNumberWhereDrawWasCalled(-1) + , m_consecutiveFailedDraws(0) + , m_maximumNumberOfFailedDrawsBeforeDrawIsForced(3) + , m_needsRedraw(false) + , m_needsForcedRedraw(false) + , m_needsForcedRedrawAfterNextCommit(false) + , m_needsCommit(false) + , m_needsForcedCommit(false) + , m_mainThreadNeedsLayerTextures(false) + , m_updateMoreResourcesPending(false) + , m_insideVSync(false) + , m_visible(false) + , m_canBeginFrame(false) + , m_canDraw(true) + , m_drawIfPossibleFailed(false) + , m_textureState(LAYER_TEXTURE_STATE_UNLOCKED) + , m_contextState(CONTEXT_ACTIVE) +{ +} + +bool CCSchedulerStateMachine::hasDrawnThisFrame() const +{ + return m_currentFrameNumber == m_lastFrameNumberWhereDrawWasCalled; +} + +bool CCSchedulerStateMachine::drawSuspendedUntilCommit() const +{ + if (!m_canDraw) + return true; + if (!m_visible) + return true; + if (m_textureState == LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD) + return true; + return false; +} + +bool CCSchedulerStateMachine::scheduledToDraw() const +{ + if (!m_needsRedraw) + return false; + if (drawSuspendedUntilCommit()) + return false; + return true; +} + +bool CCSchedulerStateMachine::shouldDraw() const +{ + if (m_needsForcedRedraw) + return true; + + if (!scheduledToDraw()) + return false; + if (!m_insideVSync) + return false; + if (hasDrawnThisFrame()) + return false; + if (m_contextState != CONTEXT_ACTIVE) + return false; + return true; +} + +bool CCSchedulerStateMachine::shouldAcquireLayerTexturesForMainThread() const +{ + if (!m_mainThreadNeedsLayerTextures) + return false; + if (m_textureState == LAYER_TEXTURE_STATE_UNLOCKED) + return true; + ASSERT(m_textureState == LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD); + // Transfer the lock from impl thread to main thread immediately if the + // impl thread is not even scheduled to draw. Guards against deadlocking. + if (!scheduledToDraw()) + return true; + if (!vsyncCallbackNeeded()) + return true; + return false; +} + +CCSchedulerStateMachine::Action CCSchedulerStateMachine::nextAction() const +{ + if (shouldAcquireLayerTexturesForMainThread()) + return ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD; + switch (m_commitState) { + case COMMIT_STATE_IDLE: + if (m_contextState != CONTEXT_ACTIVE && m_needsForcedRedraw) + return ACTION_DRAW_FORCED; + if (m_contextState != CONTEXT_ACTIVE && m_needsForcedCommit) + return ACTION_BEGIN_FRAME; + if (m_contextState == CONTEXT_LOST) + return ACTION_BEGIN_CONTEXT_RECREATION; + if (m_contextState == CONTEXT_RECREATING) + return ACTION_NONE; + if (shouldDraw()) + return m_needsForcedRedraw ? ACTION_DRAW_FORCED : ACTION_DRAW_IF_POSSIBLE; + if (m_needsCommit && ((m_visible && m_canBeginFrame) || m_needsForcedCommit)) + return ACTION_BEGIN_FRAME; + return ACTION_NONE; + + case COMMIT_STATE_FRAME_IN_PROGRESS: + if (shouldDraw()) + return m_needsForcedRedraw ? ACTION_DRAW_FORCED : ACTION_DRAW_IF_POSSIBLE; + return ACTION_NONE; + + case COMMIT_STATE_UPDATING_RESOURCES: + if (shouldDraw()) + return m_needsForcedRedraw ? ACTION_DRAW_FORCED : ACTION_DRAW_IF_POSSIBLE; + if (!m_updateMoreResourcesPending) + return ACTION_BEGIN_UPDATE_MORE_RESOURCES; + return ACTION_NONE; + + case COMMIT_STATE_READY_TO_COMMIT: + return ACTION_COMMIT; + + case COMMIT_STATE_WAITING_FOR_FIRST_DRAW: + if (shouldDraw() || m_contextState == CONTEXT_LOST) + return m_needsForcedRedraw ? ACTION_DRAW_FORCED : ACTION_DRAW_IF_POSSIBLE; + // COMMIT_STATE_WAITING_FOR_FIRST_DRAW wants to enforce a draw. If m_canDraw is false + // or textures are not available, proceed to the next step (similar as in COMMIT_STATE_IDLE). + bool canCommit = m_visible || m_needsForcedCommit; + if (m_needsCommit && canCommit && drawSuspendedUntilCommit()) + return ACTION_BEGIN_FRAME; + return ACTION_NONE; + } + ASSERT_NOT_REACHED(); + return ACTION_NONE; +} + +void CCSchedulerStateMachine::updateState(Action action) +{ + switch (action) { + case ACTION_NONE: + return; + + case ACTION_BEGIN_FRAME: + ASSERT(m_visible || m_needsForcedCommit); + m_commitState = COMMIT_STATE_FRAME_IN_PROGRESS; + m_needsCommit = false; + m_needsForcedCommit = false; + return; + + case ACTION_BEGIN_UPDATE_MORE_RESOURCES: + ASSERT(m_commitState == COMMIT_STATE_UPDATING_RESOURCES); + m_updateMoreResourcesPending = true; + return; + + case ACTION_COMMIT: + if ((m_needsCommit || !m_visible) && !m_needsForcedCommit) + m_commitState = COMMIT_STATE_WAITING_FOR_FIRST_DRAW; + else + m_commitState = COMMIT_STATE_IDLE; + + m_needsRedraw = true; + if (m_drawIfPossibleFailed) + m_lastFrameNumberWhereDrawWasCalled = -1; + + if (m_needsForcedRedrawAfterNextCommit) { + m_needsForcedRedrawAfterNextCommit = false; + m_needsForcedRedraw = true; + } + + m_textureState = LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD; + return; + + case ACTION_DRAW_FORCED: + case ACTION_DRAW_IF_POSSIBLE: + m_needsRedraw = false; + m_needsForcedRedraw = false; + m_drawIfPossibleFailed = false; + if (m_insideVSync) + m_lastFrameNumberWhereDrawWasCalled = m_currentFrameNumber; + if (m_commitState == COMMIT_STATE_WAITING_FOR_FIRST_DRAW) + m_commitState = COMMIT_STATE_IDLE; + if (m_textureState == LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD) + m_textureState = LAYER_TEXTURE_STATE_UNLOCKED; + return; + + case ACTION_BEGIN_CONTEXT_RECREATION: + ASSERT(m_commitState == COMMIT_STATE_IDLE); + ASSERT(m_contextState == CONTEXT_LOST); + m_contextState = CONTEXT_RECREATING; + return; + + case ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD: + m_textureState = LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD; + m_mainThreadNeedsLayerTextures = false; + if (m_commitState != COMMIT_STATE_FRAME_IN_PROGRESS) + m_needsCommit = true; + return; + } +} + +void CCSchedulerStateMachine::setMainThreadNeedsLayerTextures() +{ + ASSERT(!m_mainThreadNeedsLayerTextures); + ASSERT(m_textureState != LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD); + m_mainThreadNeedsLayerTextures = true; +} + +bool CCSchedulerStateMachine::vsyncCallbackNeeded() const +{ + if (!m_visible || m_contextState != CONTEXT_ACTIVE) { + if (m_needsForcedRedraw || m_commitState == COMMIT_STATE_UPDATING_RESOURCES) + return true; + + return false; + } + + return m_needsRedraw || m_needsForcedRedraw || m_commitState == COMMIT_STATE_UPDATING_RESOURCES; +} + +void CCSchedulerStateMachine::didEnterVSync() +{ + m_insideVSync = true; +} + +void CCSchedulerStateMachine::didLeaveVSync() +{ + m_currentFrameNumber++; + m_insideVSync = false; +} + +void CCSchedulerStateMachine::setVisible(bool visible) +{ + m_visible = visible; +} + +void CCSchedulerStateMachine::setNeedsRedraw() +{ + m_needsRedraw = true; +} + +void CCSchedulerStateMachine::setNeedsForcedRedraw() +{ + m_needsForcedRedraw = true; +} + +void CCSchedulerStateMachine::didDrawIfPossibleCompleted(bool success) +{ + m_drawIfPossibleFailed = !success; + if (m_drawIfPossibleFailed) { + m_needsRedraw = true; + m_needsCommit = true; + m_consecutiveFailedDraws++; + if (m_consecutiveFailedDraws >= m_maximumNumberOfFailedDrawsBeforeDrawIsForced) { + m_consecutiveFailedDraws = 0; + // We need to force a draw, but it doesn't make sense to do this until + // we've committed and have new textures. + m_needsForcedRedrawAfterNextCommit = true; + } + } else + m_consecutiveFailedDraws = 0; +} + +void CCSchedulerStateMachine::setNeedsCommit() +{ + m_needsCommit = true; +} + +void CCSchedulerStateMachine::setNeedsForcedCommit() +{ + m_needsForcedCommit = true; +} + +void CCSchedulerStateMachine::beginFrameComplete() +{ + ASSERT(m_commitState == COMMIT_STATE_FRAME_IN_PROGRESS); + m_commitState = COMMIT_STATE_UPDATING_RESOURCES; +} + +void CCSchedulerStateMachine::beginFrameAborted() +{ + ASSERT(m_commitState == COMMIT_STATE_FRAME_IN_PROGRESS); + m_commitState = COMMIT_STATE_IDLE; +} + +void CCSchedulerStateMachine::beginUpdateMoreResourcesComplete(bool morePending) +{ + ASSERT(m_commitState == COMMIT_STATE_UPDATING_RESOURCES); + ASSERT(m_updateMoreResourcesPending); + m_updateMoreResourcesPending = false; + if (!morePending) + m_commitState = COMMIT_STATE_READY_TO_COMMIT; +} + +void CCSchedulerStateMachine::didLoseContext() +{ + if (m_contextState == CONTEXT_LOST || m_contextState == CONTEXT_RECREATING) + return; + m_contextState = CONTEXT_LOST; +} + +void CCSchedulerStateMachine::didRecreateContext() +{ + ASSERT(m_contextState == CONTEXT_RECREATING); + m_contextState = CONTEXT_ACTIVE; + setNeedsCommit(); +} + +void CCSchedulerStateMachine::setMaximumNumberOfFailedDrawsBeforeDrawIsForced(int numDraws) +{ + m_maximumNumberOfFailedDrawsBeforeDrawIsForced = numDraws; +} + +} diff --git a/cc/CCSchedulerStateMachine.h b/cc/CCSchedulerStateMachine.h new file mode 100644 index 0000000..279b716 --- /dev/null +++ b/cc/CCSchedulerStateMachine.h @@ -0,0 +1,163 @@ +// 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. + +#ifndef CCSchedulerStateMachine_h +#define CCSchedulerStateMachine_h + +#include <wtf/Noncopyable.h> + +namespace WebCore { + +// The CCSchedulerStateMachine decides how to coordinate main thread activites +// like painting/running javascript with rendering and input activities on the +// impl thread. +// +// The state machine tracks internal state but is also influenced by external state. +// Internal state includes things like whether a frame has been requested, while +// external state includes things like the current time being near to the vblank time. +// +// The scheduler seperates "what to do next" from the updating of its internal state to +// make testing cleaner. +class CCSchedulerStateMachine { +public: + CCSchedulerStateMachine(); + + enum CommitState { + COMMIT_STATE_IDLE, + COMMIT_STATE_FRAME_IN_PROGRESS, + COMMIT_STATE_UPDATING_RESOURCES, + COMMIT_STATE_READY_TO_COMMIT, + COMMIT_STATE_WAITING_FOR_FIRST_DRAW, + }; + + enum TextureState { + LAYER_TEXTURE_STATE_UNLOCKED, + LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD, + LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD, + }; + + enum ContextState { + CONTEXT_ACTIVE, + CONTEXT_LOST, + CONTEXT_RECREATING, + }; + + bool commitPending() const + { + return m_commitState != COMMIT_STATE_IDLE; + } + + bool redrawPending() const { return m_needsRedraw; } + + enum Action { + ACTION_NONE, + ACTION_BEGIN_FRAME, + ACTION_BEGIN_UPDATE_MORE_RESOURCES, + ACTION_COMMIT, + ACTION_DRAW_IF_POSSIBLE, + ACTION_DRAW_FORCED, + ACTION_BEGIN_CONTEXT_RECREATION, + ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD, + }; + Action nextAction() const; + void updateState(Action); + + // Indicates whether the scheduler needs a vsync callback in order to make + // progress. + bool vsyncCallbackNeeded() const; + + // Indicates that the system has entered and left a vsync callback. + // The scheduler will not draw more than once in a given vsync callback. + void didEnterVSync(); + void didLeaveVSync(); + + // Indicates whether the LayerTreeHostImpl is visible. + void setVisible(bool); + + // Indicates that a redraw is required, either due to the impl tree changing + // or the screen being damaged and simply needing redisplay. + void setNeedsRedraw(); + + // As setNeedsRedraw(), but ensures the draw will definitely happen even if + // we are not visible. + void setNeedsForcedRedraw(); + + // Indicates whether ACTION_DRAW_IF_POSSIBLE drew to the screen or not. + void didDrawIfPossibleCompleted(bool success); + + // Indicates that a new commit flow needs to be performed, either to pull + // updates from the main thread to the impl, or to push deltas from the impl + // thread to main. + void setNeedsCommit(); + + // As setNeedsCommit(), but ensures the beginFrame will definitely happen even if + // we are not visible. + void setNeedsForcedCommit(); + + // Call this only in response to receiving an ACTION_BEGIN_FRAME + // from nextState. Indicates that all painting is complete and that + // updating of compositor resources can begin. + void beginFrameComplete(); + + // Call this only in response to receiving an ACTION_BEGIN_FRAME + // from nextState if the client rejects the beginFrame message. + void beginFrameAborted(); + + // Call this only in response to receiving an ACTION_UPDATE_MORE_RESOURCES + // from nextState. Indicates that the specific update request completed. + void beginUpdateMoreResourcesComplete(bool morePending); + + // Request exclusive access to the textures that back single buffered + // layers on behalf of the main thread. Upon acqusition, + // ACTION_DRAW_IF_POSSIBLE will not draw until the main thread releases the + // textures to the impl thread by committing the layers. + void setMainThreadNeedsLayerTextures(); + + // Indicates whether we can successfully begin a frame at this time. + void setCanBeginFrame(bool can) { m_canBeginFrame = can; } + + // Indicates whether drawing would, at this time, make sense. + // canDraw can be used to supress flashes or checkerboarding + // when such behavior would be undesirable. + void setCanDraw(bool can) { m_canDraw = can; } + + void didLoseContext(); + void didRecreateContext(); + + // Exposed for testing purposes. + void setMaximumNumberOfFailedDrawsBeforeDrawIsForced(int); + +protected: + bool shouldDrawForced() const; + bool drawSuspendedUntilCommit() const; + bool scheduledToDraw() const; + bool shouldDraw() const; + bool shouldAcquireLayerTexturesForMainThread() const; + bool hasDrawnThisFrame() const; + + CommitState m_commitState; + + int m_currentFrameNumber; + int m_lastFrameNumberWhereDrawWasCalled; + int m_consecutiveFailedDraws; + int m_maximumNumberOfFailedDrawsBeforeDrawIsForced; + bool m_needsRedraw; + bool m_needsForcedRedraw; + bool m_needsForcedRedrawAfterNextCommit; + bool m_needsCommit; + bool m_needsForcedCommit; + bool m_mainThreadNeedsLayerTextures; + bool m_updateMoreResourcesPending; + bool m_insideVSync; + bool m_visible; + bool m_canBeginFrame; + bool m_canDraw; + bool m_drawIfPossibleFailed; + TextureState m_textureState; + ContextState m_contextState; +}; + +} + +#endif // CCSchedulerStateMachine_h diff --git a/cc/CCSchedulerStateMachineTest.cpp b/cc/CCSchedulerStateMachineTest.cpp new file mode 100644 index 0000000..7289189 --- /dev/null +++ b/cc/CCSchedulerStateMachineTest.cpp @@ -0,0 +1,1045 @@ +// 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 "config.h" + +#include "CCSchedulerStateMachine.h" + +#include <gtest/gtest.h> +#include <wtf/text/CString.h> +#include <wtf/text/WTFString.h> + +using namespace WTF; +using namespace WebCore; + +namespace { + +const CCSchedulerStateMachine::CommitState allCommitStates[] = { + CCSchedulerStateMachine::COMMIT_STATE_IDLE, + CCSchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS, + CCSchedulerStateMachine::COMMIT_STATE_UPDATING_RESOURCES, + CCSchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT, + CCSchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW +}; + +// Exposes the protected state fields of the CCSchedulerStateMachine for testing +class StateMachine : public CCSchedulerStateMachine { +public: + void setCommitState(CommitState cs) { m_commitState = cs; } + CommitState commitState() const { return m_commitState; } + + void setNeedsCommit(bool b) { m_needsCommit = b; } + bool needsCommit() const { return m_needsCommit; } + + void setNeedsForcedCommit(bool b) { m_needsForcedCommit = b; } + bool needsForcedCommit() const { return m_needsForcedCommit; } + + void setNeedsRedraw(bool b) { m_needsRedraw = b; } + bool needsRedraw() const { return m_needsRedraw; } + + void setNeedsForcedRedraw(bool b) { m_needsForcedRedraw = b; } + bool needsForcedRedraw() const { return m_needsForcedRedraw; } + + bool canDraw() const { return m_canDraw; } + bool insideVSync() const { return m_insideVSync; } + bool visible() const { return m_visible; } + + void setUpdateMoreResourcesPending(bool b) { m_updateMoreResourcesPending = b; } + bool updateMoreResourcesPending() const { return m_updateMoreResourcesPending; } +}; + +TEST(CCSchedulerStateMachineTest, TestNextActionBeginsFrameIfNeeded) +{ + // If no commit needed, do nothing + { + StateMachine state; + state.setCommitState(CCSchedulerStateMachine::COMMIT_STATE_IDLE); + state.setCanBeginFrame(true); + state.setNeedsRedraw(false); + state.setNeedsCommit(false); + state.setUpdateMoreResourcesPending(false); + state.setVisible(true); + + EXPECT_FALSE(state.vsyncCallbackNeeded()); + + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + EXPECT_FALSE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + } + + // If commit requested but canBeginFrame is still false, do nothing. + { + StateMachine state; + state.setCommitState(CCSchedulerStateMachine::COMMIT_STATE_IDLE); + state.setNeedsRedraw(false); + state.setNeedsCommit(false); + state.setUpdateMoreResourcesPending(false); + state.setVisible(true); + + EXPECT_FALSE(state.vsyncCallbackNeeded()); + + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + EXPECT_FALSE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + } + + + // If commit requested, begin a frame + { + StateMachine state; + state.setCommitState(CCSchedulerStateMachine::COMMIT_STATE_IDLE); + state.setCanBeginFrame(true); + state.setNeedsRedraw(false); + state.setNeedsCommit(true); + state.setUpdateMoreResourcesPending(false); + state.setVisible(true); + EXPECT_FALSE(state.vsyncCallbackNeeded()); + } + + // Begin the frame, make sure needsCommit and commitState update correctly. + { + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_FRAME); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS, state.commitState()); + EXPECT_FALSE(state.needsCommit()); + EXPECT_FALSE(state.vsyncCallbackNeeded()); + } +} + +TEST(CCSchedulerStateMachineTest, TestSetForcedRedrawDoesNotSetsNormalRedraw) +{ + CCSchedulerStateMachine state; + state.setNeedsForcedRedraw(); + EXPECT_FALSE(state.redrawPending()); + EXPECT_TRUE(state.vsyncCallbackNeeded()); +} + +TEST(CCSchedulerStateMachineTest, TestFailedDrawSetsNeedsCommitAndDoesNotDrawAgain) +{ + CCSchedulerStateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + state.setNeedsRedraw(); + EXPECT_TRUE(state.redrawPending()); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + + // We're drawing now. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + EXPECT_FALSE(state.redrawPending()); + EXPECT_FALSE(state.commitPending()); + + // Failing the draw makes us require a commit. + state.didDrawIfPossibleCompleted(false); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_FRAME); + EXPECT_TRUE(state.redrawPending()); + EXPECT_TRUE(state.commitPending()); +} + +TEST(CCSchedulerStateMachineTest, TestSetNeedsRedrawDuringFailedDrawDoesNotRemoveNeedsRedraw) +{ + CCSchedulerStateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + state.setNeedsRedraw(); + EXPECT_TRUE(state.redrawPending()); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + + // We're drawing now. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + EXPECT_FALSE(state.redrawPending()); + EXPECT_FALSE(state.commitPending()); + + // While still in the same vsync callback, set needs redraw again. + // This should not redraw. + state.setNeedsRedraw(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Failing the draw makes us require a commit. + state.didDrawIfPossibleCompleted(false); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + EXPECT_TRUE(state.redrawPending()); +} + +TEST(CCSchedulerStateMachineTest, TestCommitAfterFailedDrawAllowsDrawInSameFrame) +{ + CCSchedulerStateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + + // Start a commit. + state.setNeedsCommit(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_FRAME); + EXPECT_TRUE(state.commitPending()); + + // Then initiate a draw. + state.setNeedsRedraw(); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + EXPECT_TRUE(state.redrawPending()); + + // Fail the draw. + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.didDrawIfPossibleCompleted(false); + EXPECT_TRUE(state.redrawPending()); + // But the commit is ongoing. + EXPECT_TRUE(state.commitPending()); + + // Finish the commit. + state.beginFrameComplete(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + state.beginUpdateMoreResourcesComplete(false); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_COMMIT); + EXPECT_TRUE(state.redrawPending()); + + // And we should be allowed to draw again. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestCommitAfterFailedAndSuccessfulDrawDoesNotAllowDrawInSameFrame) +{ + CCSchedulerStateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + + // Start a commit. + state.setNeedsCommit(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_FRAME); + EXPECT_TRUE(state.commitPending()); + + // Then initiate a draw. + state.setNeedsRedraw(); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + EXPECT_TRUE(state.redrawPending()); + + // Fail the draw. + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.didDrawIfPossibleCompleted(false); + EXPECT_TRUE(state.redrawPending()); + // But the commit is ongoing. + EXPECT_TRUE(state.commitPending()); + + // Force a draw. + state.setNeedsForcedRedraw(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_FORCED, state.nextAction()); + + // Do the forced draw. + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_FORCED); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + EXPECT_FALSE(state.redrawPending()); + // And the commit is still ongoing. + EXPECT_TRUE(state.commitPending()); + + // Finish the commit. + state.beginFrameComplete(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + state.beginUpdateMoreResourcesComplete(false); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_COMMIT); + EXPECT_TRUE(state.redrawPending()); + + // And we should not be allowed to draw again in the same frame.. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestFailedDrawsWillEventuallyForceADrawAfterTheNextCommit) +{ + CCSchedulerStateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + state.setMaximumNumberOfFailedDrawsBeforeDrawIsForced(1); + + // Start a commit. + state.setNeedsCommit(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_FRAME); + EXPECT_TRUE(state.commitPending()); + + // Then initiate a draw. + state.setNeedsRedraw(); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + EXPECT_TRUE(state.redrawPending()); + + // Fail the draw. + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.didDrawIfPossibleCompleted(false); + EXPECT_TRUE(state.redrawPending()); + // But the commit is ongoing. + EXPECT_TRUE(state.commitPending()); + + // Finish the commit. Note, we should not yet be forcing a draw, but should + // continue the commit as usual. + state.beginFrameComplete(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + state.beginUpdateMoreResourcesComplete(false); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_COMMIT); + EXPECT_TRUE(state.redrawPending()); + + // The redraw should be forced in this case. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_FORCED, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestFailedDrawIsRetriedNextVSync) +{ + CCSchedulerStateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + + // Start a draw. + state.setNeedsRedraw(); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + EXPECT_TRUE(state.redrawPending()); + + // Fail the draw. + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.didDrawIfPossibleCompleted(false); + EXPECT_TRUE(state.redrawPending()); + + // We should not be trying to draw again now, but we have a commit pending. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + + state.didLeaveVSync(); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + + // We should try draw again in the next vsync. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestDoestDrawTwiceInSameFrame) +{ + CCSchedulerStateMachine state; + state.setVisible(true); + state.setNeedsRedraw(); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + + // While still in the same vsync callback, set needs redraw again. + // This should not redraw. + state.setNeedsRedraw(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Move to another frame. This should now draw. + state.didDrawIfPossibleCompleted(true); + state.didLeaveVSync(); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + state.didEnterVSync(); + + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + state.didDrawIfPossibleCompleted(true); + EXPECT_FALSE(state.vsyncCallbackNeeded()); +} + +TEST(CCSchedulerStateMachineTest, TestNextActionDrawsOnVSync) +{ + // When not on vsync, or on vsync but not visible, don't draw. + size_t numCommitStates = sizeof(allCommitStates) / sizeof(CCSchedulerStateMachine::CommitState); + for (size_t i = 0; i < numCommitStates; ++i) { + for (unsigned j = 0; j < 2; ++j) { + StateMachine state; + state.setCommitState(allCommitStates[i]); + bool visible = j; + if (!visible) { + state.didEnterVSync(); + state.setVisible(false); + } else + state.setVisible(true); + + // Case 1: needsCommit=false + state.setNeedsCommit(false); + EXPECT_NE(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + + // Case 2: needsCommit=true + state.setNeedsCommit(true); + EXPECT_NE(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + } + } + + // When on vsync, or not on vsync but needsForcedRedraw set, should always draw except if you're ready to commit, in which case commit. + for (size_t i = 0; i < numCommitStates; ++i) { + for (unsigned j = 0; j < 2; ++j) { + StateMachine state; + state.setCommitState(allCommitStates[i]); + bool forcedDraw = j; + if (!forcedDraw) { + state.didEnterVSync(); + state.setNeedsRedraw(true); + state.setVisible(true); + } else + state.setNeedsForcedRedraw(true); + + CCSchedulerStateMachine::Action expectedAction; + if (allCommitStates[i] != CCSchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT) + expectedAction = forcedDraw ? CCSchedulerStateMachine::ACTION_DRAW_FORCED : CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE; + else + expectedAction = CCSchedulerStateMachine::ACTION_COMMIT; + + // Case 1: needsCommit=false updateMoreResourcesPending=false. + state.setNeedsCommit(false); + state.setUpdateMoreResourcesPending(false); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + EXPECT_EQ(expectedAction, state.nextAction()); + + // Case 2: needsCommit=false updateMoreResourcesPending=true. + state.setNeedsCommit(false); + state.setUpdateMoreResourcesPending(true); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + EXPECT_EQ(expectedAction, state.nextAction()); + + // Case 3: needsCommit=true updateMoreResourcesPending=false. + state.setNeedsCommit(true); + state.setUpdateMoreResourcesPending(false); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + EXPECT_EQ(expectedAction, state.nextAction()); + + // Case 4: needsCommit=true updateMoreResourcesPending=true. + state.setNeedsCommit(true); + state.setUpdateMoreResourcesPending(true); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + EXPECT_EQ(expectedAction, state.nextAction()); + } + } +} + +TEST(CCSchedulerStateMachineTest, TestNoCommitStatesRedrawWhenInvisible) +{ + size_t numCommitStates = sizeof(allCommitStates) / sizeof(CCSchedulerStateMachine::CommitState); + for (size_t i = 0; i < numCommitStates; ++i) { + // There shouldn't be any drawing regardless of vsync. + for (unsigned j = 0; j < 2; ++j) { + StateMachine state; + state.setCommitState(allCommitStates[i]); + state.setVisible(false); + state.setNeedsRedraw(true); + state.setNeedsForcedRedraw(false); + if (j == 1) + state.didEnterVSync(); + + // Case 1: needsCommit=false updateMoreResourcesPending=false. + state.setNeedsCommit(false); + state.setUpdateMoreResourcesPending(false); + EXPECT_NE(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + + // Case 2: needsCommit=false updateMoreResourcesPending=true. + state.setNeedsCommit(false); + state.setUpdateMoreResourcesPending(true); + EXPECT_NE(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + + // Case 3: needsCommit=true updateMoreResourcesPending=false. + state.setNeedsCommit(true); + state.setUpdateMoreResourcesPending(false); + EXPECT_NE(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + + // Case 4: needsCommit=true updateMoreResourcesPending=true. + state.setNeedsCommit(true); + state.setUpdateMoreResourcesPending(true); + EXPECT_NE(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + } + } +} + +TEST(CCSchedulerStateMachineTest, TestCanRedraw_StopsDraw) +{ + size_t numCommitStates = sizeof(allCommitStates) / sizeof(CCSchedulerStateMachine::CommitState); + for (size_t i = 0; i < numCommitStates; ++i) { + // There shouldn't be any drawing regardless of vsync. + for (unsigned j = 0; j < 2; ++j) { + StateMachine state; + state.setCommitState(allCommitStates[i]); + state.setVisible(false); + state.setNeedsRedraw(true); + state.setNeedsForcedRedraw(false); + if (j == 1) + state.didEnterVSync(); + + state.setCanDraw(false); + EXPECT_NE(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + } + } +} + +TEST(CCSchedulerStateMachineTest, TestCanRedrawWithWaitingForFirstDrawMakesProgress) +{ + StateMachine state; + state.setCommitState(CCSchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW); + state.setCanBeginFrame(true); + state.setNeedsCommit(true); + state.setNeedsRedraw(true); + state.setUpdateMoreResourcesPending(false); + state.setVisible(true); + state.setCanDraw(false); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestUpdates_NoRedraw_OneRoundOfUpdates) +{ + StateMachine state; + state.setCommitState(CCSchedulerStateMachine::COMMIT_STATE_UPDATING_RESOURCES); + state.setNeedsRedraw(false); + state.setUpdateMoreResourcesPending(false); + state.setVisible(true); + + // Verify we begin update, both for vsync and not vsync. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + + // Begin an update. + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + + // Verify we don't do anything, both for vsync and not vsync. + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // End update with no more updates pending. + state.beginUpdateMoreResourcesComplete(false); + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestUpdates_NoRedraw_TwoRoundsOfUpdates) +{ + StateMachine state; + state.setCommitState(CCSchedulerStateMachine::COMMIT_STATE_UPDATING_RESOURCES); + state.setNeedsRedraw(false); + state.setUpdateMoreResourcesPending(false); + state.setVisible(true); + + // Verify the update begins, both for vsync and not vsync. + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + + // Begin an update. + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + + // Verify we do nothing, both for vsync and not vsync. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Ack the update with more pending. + state.beginUpdateMoreResourcesComplete(true); + + // Verify we update more, both for vsync and not vsync. + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + + // Begin another update, while inside vsync. And, it updating. + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + state.beginUpdateMoreResourcesComplete(false); + + // Make sure we commit, independent of vsync. + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); +} + + +TEST(CCSchedulerStateMachineTest, TestVSyncNeededWhenUpdatesPendingButInvisible) +{ + StateMachine state; + state.setCommitState(CCSchedulerStateMachine::COMMIT_STATE_UPDATING_RESOURCES); + state.setNeedsRedraw(false); + state.setVisible(false); + state.setUpdateMoreResourcesPending(true); + EXPECT_TRUE(state.vsyncCallbackNeeded()); + + state.setUpdateMoreResourcesPending(false); + EXPECT_TRUE(state.vsyncCallbackNeeded()); +} + +TEST(CCSchedulerStateMachineTest, TestUpdates_WithRedraw_OneRoundOfUpdates) +{ + StateMachine state; + state.setCommitState(CCSchedulerStateMachine::COMMIT_STATE_UPDATING_RESOURCES); + state.setNeedsRedraw(true); + state.setUpdateMoreResourcesPending(false); + state.setVisible(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + + // Begin an update. + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + + // Ensure we draw on the next vsync even though an update is in-progress. + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + state.didDrawIfPossibleCompleted(true); + + // Ensure that we once we have drawn, we dont do anything else. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Leave the vsync before we finish the update. + state.didLeaveVSync(); + + // Finish update but leave more resources pending. + state.beginUpdateMoreResourcesComplete(true); + + // Verify that regardless of vsync, we update some more. + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + + // Begin another update. Finish it immediately. Inside the vsync. + state.didEnterVSync(); + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + state.didLeaveVSync(); + state.beginUpdateMoreResourcesComplete(false); + + // Verify we commit regardless of vsync state + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestSetNeedsCommitIsNotLost) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setNeedsCommit(true); + state.setVisible(true); + + // Begin the frame. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + state.updateState(state.nextAction()); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS, state.commitState()); + + // Now, while the frame is in progress, set another commit. + state.setNeedsCommit(true); + EXPECT_TRUE(state.needsCommit()); + + // Let the frame finish. + state.beginFrameComplete(); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_UPDATING_RESOURCES, state.commitState()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.beginUpdateMoreResourcesComplete(false); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT, state.commitState()); + + // Expect to commit regardless of vsync state. + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + + // Commit and make sure we draw on next vsync + state.updateState(CCSchedulerStateMachine::ACTION_COMMIT); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW, state.commitState()); + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + state.didDrawIfPossibleCompleted(true); + + // Verify that another commit will begin. + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestFullCycle) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + + // Start clean and set commit. + state.setNeedsCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + + // Begin the frame. + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_FRAME); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS, state.commitState()); + EXPECT_FALSE(state.needsCommit()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Tell the scheduler the frame finished. + state.beginFrameComplete(); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_UPDATING_RESOURCES, state.commitState()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + + // Tell the scheduler the update began and finished + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + state.beginUpdateMoreResourcesComplete(false); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT, state.commitState()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + + // Commit. + state.updateState(CCSchedulerStateMachine::ACTION_COMMIT); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_IDLE, state.commitState()); + EXPECT_TRUE(state.needsRedraw()); + + // Expect to do nothing until vsync. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // At vsync, draw. + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + state.didDrawIfPossibleCompleted(true); + state.didLeaveVSync(); + + // Should be synchronized, no draw needed, no action needed. + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_IDLE, state.commitState()); + EXPECT_FALSE(state.needsRedraw()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestFullCycleWithCommitRequestInbetween) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + + // Start clean and set commit. + state.setNeedsCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + + // Begin the frame. + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_FRAME); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS, state.commitState()); + EXPECT_FALSE(state.needsCommit()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Request another commit while the commit is in flight. + state.setNeedsCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Tell the scheduler the frame finished. + state.beginFrameComplete(); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_UPDATING_RESOURCES, state.commitState()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + + // Tell the scheduler the update began and finished + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES); + state.beginUpdateMoreResourcesComplete(false); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT, state.commitState()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + + // Commit. + state.updateState(CCSchedulerStateMachine::ACTION_COMMIT); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW, state.commitState()); + EXPECT_TRUE(state.needsRedraw()); + + // Expect to do nothing until vsync. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // At vsync, draw. + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.updateState(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE); + state.didDrawIfPossibleCompleted(true); + state.didLeaveVSync(); + + // Should be synchronized, no draw needed, no action needed. + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_IDLE, state.commitState()); + EXPECT_FALSE(state.needsRedraw()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestRequestCommitInvisible) +{ + StateMachine state; + state.setNeedsCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestGoesInvisibleBeforeBeginFrameCompletes) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + + // Start clean and set commit. + state.setNeedsCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + + // Begin the frame while visible. + state.updateState(CCSchedulerStateMachine::ACTION_BEGIN_FRAME); + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS, state.commitState()); + EXPECT_FALSE(state.needsCommit()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Become invisible and abort the beginFrame. + state.setVisible(false); + state.beginFrameAborted(); + + // We should now be back in the idle state as if we didn't start a frame at all. + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_IDLE, state.commitState()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestContextLostWhenCompletelyIdle) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + + state.didLoseContext(); + + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_CONTEXT_RECREATION, state.nextAction()); + state.updateState(state.nextAction()); + + // Once context recreation begins, nothing should happen. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Recreate the context + state.didRecreateContext(); + + // When the context is recreated, we should begin a commit + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + state.updateState(state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestContextLostWhenIdleAndCommitRequestedWhileRecreating) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + + state.didLoseContext(); + + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_CONTEXT_RECREATION, state.nextAction()); + state.updateState(state.nextAction()); + + // Once context recreation begins, nothing should happen. + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // While context is recreating, commits shouldn't begin. + state.setNeedsCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Recreate the context + state.didRecreateContext(); + + // When the context is recreated, we should begin a commit + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + state.updateState(state.nextAction()); + + // Once the context is recreated, whether we draw should be based on + // setCanDraw. + state.setNeedsRedraw(true); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.setCanDraw(false); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.setCanDraw(true); + state.didLeaveVSync(); +} + +TEST(CCSchedulerStateMachineTest, TestContextLostWhileCommitInProgress) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + + // Get a commit in flight. + state.setNeedsCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + state.updateState(state.nextAction()); + + // Set damage and expect a draw. + state.setNeedsRedraw(true); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.updateState(state.nextAction()); + state.didLeaveVSync(); + + // Cause a lost context while the begin frame is in flight. + state.didLoseContext(); + + // Ask for another draw. Expect nothing happens. + state.setNeedsRedraw(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Finish the frame, update resources, and commit. + state.beginFrameComplete(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.updateState(state.nextAction()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.beginUpdateMoreResourcesComplete(false); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + state.updateState(state.nextAction()); + + // Expect to be told to begin context recreation, independent of vsync state + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_CONTEXT_RECREATION, state.nextAction()); + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_CONTEXT_RECREATION, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestContextLostWhileCommitInProgressAndAnotherCommitRequested) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + + // Get a commit in flight. + state.setNeedsCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); + state.updateState(state.nextAction()); + + // Set damage and expect a draw. + state.setNeedsRedraw(true); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.updateState(state.nextAction()); + state.didLeaveVSync(); + + // Cause a lost context while the begin frame is in flight. + state.didLoseContext(); + + // Ask for another draw and also set needs commit. Expect nothing happens. + // Setting another commit will put us into + // COMMIT_STATE_WAITING_FOR_FIRST_DRAW after we finish the frame on the main + // thread. + state.setNeedsRedraw(true); + state.setNeedsCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + + // Finish the frame, update resources, and commit. + state.beginFrameComplete(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.updateState(state.nextAction()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.beginUpdateMoreResourcesComplete(false); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + state.updateState(state.nextAction()); + + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW, state.commitState()); + + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.nextAction()); + state.updateState(state.nextAction()); + + // Expect to be told to begin context recreation, independent of vsync state + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_CONTEXT_RECREATION, state.nextAction()); + state.didLeaveVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_CONTEXT_RECREATION, state.nextAction()); +} + + +TEST(CCSchedulerStateMachineTest, TestFinishAllRenderingWhileContextLost) +{ + StateMachine state; + state.setVisible(true); + + // Cause a lost context lost. + state.didLoseContext(); + + // Ask a forced redraw and verify it ocurrs. + state.setNeedsForcedRedraw(true); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_FORCED, state.nextAction()); + state.didLeaveVSync(); + + // Clear the forced redraw bit. + state.setNeedsForcedRedraw(false); + + // Expect to be told to begin context recreation, independent of vsync state + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_CONTEXT_RECREATION, state.nextAction()); + state.updateState(state.nextAction()); + + // Ask a forced redraw and verify it ocurrs. + state.setNeedsForcedRedraw(true); + state.didEnterVSync(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_DRAW_FORCED, state.nextAction()); + state.didLeaveVSync(); +} + +TEST(CCSchedulerStateMachineTest, TestBeginFrameWhenInvisibleAndForceCommit) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(false); + state.setNeedsCommit(true); + state.setNeedsForcedCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestBeginFrameWhenCanBeginFrameFalseAndForceCommit) +{ + StateMachine state; + state.setVisible(true); + state.setNeedsCommit(true); + state.setNeedsForcedCommit(true); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestBeginFrameWhenCommitInProgress) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(false); + state.setCommitState(CCSchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS); + state.setNeedsCommit(true); + state.setNeedsForcedCommit(true); + + state.beginFrameComplete(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES, state.nextAction()); + state.updateState(state.nextAction()); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_NONE, state.nextAction()); + state.beginUpdateMoreResourcesComplete(false); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_COMMIT, state.nextAction()); + state.updateState(state.nextAction()); + + EXPECT_EQ(CCSchedulerStateMachine::COMMIT_STATE_IDLE, state.commitState()); + + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); +} + +TEST(CCSchedulerStateMachineTest, TestBeginFrameWhenContextLost) +{ + StateMachine state; + state.setCanBeginFrame(true); + state.setVisible(true); + state.setNeedsCommit(true); + state.setNeedsForcedCommit(true); + state.didLoseContext(); + EXPECT_EQ(CCSchedulerStateMachine::ACTION_BEGIN_FRAME, state.nextAction()); +} + +} diff --git a/cc/CCSchedulerTest.cpp b/cc/CCSchedulerTest.cpp new file mode 100644 index 0000000..ec68c71 --- /dev/null +++ b/cc/CCSchedulerTest.cpp @@ -0,0 +1,472 @@ +// 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 "config.h" + +#include "CCScheduler.h" + +#include "CCSchedulerTestCommon.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <wtf/OwnPtr.h> + +using namespace WTF; +using namespace WebCore; +using namespace WebKitTests; + +namespace { + +class FakeCCSchedulerClient : public CCSchedulerClient { +public: + FakeCCSchedulerClient() { reset(); } + void reset() + { + m_actions.clear(); + m_hasMoreResourceUpdates = false; + m_canDraw = true; + m_drawWillHappen = true; + m_swapWillHappenIfDrawHappens = true; + m_numDraws = 0; + } + + void setHasMoreResourceUpdates(bool b) { m_hasMoreResourceUpdates = b; } + void setCanDraw(bool b) { m_canDraw = b; } + + int numDraws() const { return m_numDraws; } + int numActions() const { return static_cast<int>(m_actions.size()); } + const char* action(int i) const { return m_actions[i]; } + + bool hasAction(const char* action) const + { + for (size_t i = 0; i < m_actions.size(); i++) + if (!strcmp(m_actions[i], action)) + return true; + return false; + } + + virtual bool canDraw() OVERRIDE { return m_canDraw; } + virtual bool hasMoreResourceUpdates() const OVERRIDE { return m_hasMoreResourceUpdates; } + virtual void scheduledActionBeginFrame() OVERRIDE { m_actions.push_back("scheduledActionBeginFrame"); } + virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapIfPossible() OVERRIDE + { + m_actions.push_back("scheduledActionDrawAndSwapIfPossible"); + m_numDraws++; + return CCScheduledActionDrawAndSwapResult(m_drawWillHappen, m_drawWillHappen && m_swapWillHappenIfDrawHappens); + } + + virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapForced() OVERRIDE + { + m_actions.push_back("scheduledActionDrawAndSwapForced"); + return CCScheduledActionDrawAndSwapResult(true, m_swapWillHappenIfDrawHappens); + } + + virtual void scheduledActionUpdateMoreResources(double) OVERRIDE { m_actions.push_back("scheduledActionUpdateMoreResources"); } + virtual void scheduledActionCommit() OVERRIDE { m_actions.push_back("scheduledActionCommit"); } + virtual void scheduledActionBeginContextRecreation() OVERRIDE { m_actions.push_back("scheduledActionBeginContextRecreation"); } + virtual void scheduledActionAcquireLayerTexturesForMainThread() OVERRIDE { m_actions.push_back("scheduledActionAcquireLayerTexturesForMainThread"); } + + void setDrawWillHappen(bool drawWillHappen) { m_drawWillHappen = drawWillHappen; } + void setSwapWillHappenIfDrawHappens(bool swapWillHappenIfDrawHappens) { m_swapWillHappenIfDrawHappens = swapWillHappenIfDrawHappens; } + +protected: + bool m_hasMoreResourceUpdates; + bool m_canDraw; + bool m_drawWillHappen; + bool m_swapWillHappenIfDrawHappens; + int m_numDraws; + std::vector<const char*> m_actions; +}; + +TEST(CCSchedulerTest, RequestCommit) +{ + FakeCCSchedulerClient client; + RefPtr<FakeCCTimeSource> timeSource = adoptRef(new FakeCCTimeSource()); + OwnPtr<CCScheduler> scheduler = CCScheduler::create(&client, adoptPtr(new CCFrameRateController(timeSource))); + scheduler->setCanBeginFrame(true); + scheduler->setVisible(true); + + // SetNeedsCommit should begin the frame. + scheduler->setNeedsCommit(); + EXPECT_EQ(1, client.numActions()); + EXPECT_STREQ("scheduledActionBeginFrame", client.action(0)); + EXPECT_FALSE(timeSource->active()); + client.reset(); + + // Since, hasMoreResourceUpdates is set to false, + // beginFrameComplete should commit + scheduler->beginFrameComplete(); + EXPECT_EQ(1, client.numActions()); + EXPECT_STREQ("scheduledActionCommit", client.action(0)); + EXPECT_TRUE(timeSource->active()); + client.reset(); + + // Tick should draw. + timeSource->tick(); + EXPECT_EQ(1, client.numActions()); + EXPECT_STREQ("scheduledActionDrawAndSwapIfPossible", client.action(0)); + EXPECT_FALSE(timeSource->active()); + client.reset(); + + // Timer should be off. + EXPECT_FALSE(timeSource->active()); +} + +TEST(CCSchedulerTest, RequestCommitAfterBeginFrame) +{ + FakeCCSchedulerClient client; + RefPtr<FakeCCTimeSource> timeSource = adoptRef(new FakeCCTimeSource()); + OwnPtr<CCScheduler> scheduler = CCScheduler::create(&client, adoptPtr(new CCFrameRateController(timeSource))); + scheduler->setCanBeginFrame(true); + scheduler->setVisible(true); + + // SetNedsCommit should begin the frame. + scheduler->setNeedsCommit(); + EXPECT_EQ(1, client.numActions()); + EXPECT_STREQ("scheduledActionBeginFrame", client.action(0)); + client.reset(); + + // Now setNeedsCommit again. Calling here means we need a second frame. + scheduler->setNeedsCommit(); + + // Since, hasMoreResourceUpdates is set to false, and another commit is + // needed, beginFrameComplete should commit, then begin another frame. + scheduler->beginFrameComplete(); + EXPECT_EQ(1, client.numActions()); + EXPECT_STREQ("scheduledActionCommit", client.action(0)); + client.reset(); + + // Tick should draw but then begin another frame. + timeSource->tick(); + EXPECT_FALSE(timeSource->active()); + EXPECT_EQ(2, client.numActions()); + EXPECT_STREQ("scheduledActionDrawAndSwapIfPossible", client.action(0)); + EXPECT_STREQ("scheduledActionBeginFrame", client.action(1)); + client.reset(); +} + +TEST(CCSchedulerTest, TextureAcquisitionCollision) +{ + FakeCCSchedulerClient client; + RefPtr<FakeCCTimeSource> timeSource = adoptRef(new FakeCCTimeSource()); + OwnPtr<CCScheduler> scheduler = CCScheduler::create(&client, adoptPtr(new CCFrameRateController(timeSource))); + scheduler->setCanBeginFrame(true); + scheduler->setVisible(true); + + scheduler->setNeedsCommit(); + scheduler->setMainThreadNeedsLayerTextures(); + EXPECT_EQ(2, client.numActions()); + EXPECT_STREQ("scheduledActionBeginFrame", client.action(0)); + EXPECT_STREQ("scheduledActionAcquireLayerTexturesForMainThread", client.action(1)); + client.reset(); + + // Compositor not scheduled to draw because textures are locked by main thread + EXPECT_FALSE(timeSource->active()); + + // Trigger the commit + scheduler->beginFrameComplete(); + EXPECT_TRUE(timeSource->active()); + client.reset(); + + // Between commit and draw, texture acquisition for main thread delayed, + // and main thread blocks. + scheduler->setMainThreadNeedsLayerTextures(); + EXPECT_EQ(0, client.numActions()); + client.reset(); + + // Once compositor draw complete, the delayed texture acquisition fires. + timeSource->tick(); + EXPECT_EQ(3, client.numActions()); + EXPECT_STREQ("scheduledActionDrawAndSwapIfPossible", client.action(0)); + EXPECT_STREQ("scheduledActionAcquireLayerTexturesForMainThread", client.action(1)); + EXPECT_STREQ("scheduledActionBeginFrame", client.action(2)); + client.reset(); +} + +TEST(CCSchedulerTest, VisibilitySwitchWithTextureAcquisition) +{ + FakeCCSchedulerClient client; + RefPtr<FakeCCTimeSource> timeSource = adoptRef(new FakeCCTimeSource()); + OwnPtr<CCScheduler> scheduler = CCScheduler::create(&client, adoptPtr(new CCFrameRateController(timeSource))); + scheduler->setCanBeginFrame(true); + scheduler->setVisible(true); + + scheduler->setNeedsCommit(); + scheduler->beginFrameComplete(); + scheduler->setMainThreadNeedsLayerTextures(); + client.reset(); + // Verify that pending texture acquisition fires when visibility + // is lost in order to avoid a deadlock. + scheduler->setVisible(false); + EXPECT_EQ(1, client.numActions()); + EXPECT_STREQ("scheduledActionAcquireLayerTexturesForMainThread", client.action(0)); + client.reset(); + + // Regaining visibility with textures acquired by main thread while + // compositor is waiting for first draw should result in a request + // for a new frame in order to escape a deadlock. + scheduler->setVisible(true); + EXPECT_EQ(1, client.numActions()); + EXPECT_STREQ("scheduledActionBeginFrame", client.action(0)); + client.reset(); +} + +class SchedulerClientThatSetNeedsDrawInsideDraw : public FakeCCSchedulerClient { +public: + SchedulerClientThatSetNeedsDrawInsideDraw() + : m_scheduler(0) { } + + void setScheduler(CCScheduler* scheduler) { m_scheduler = scheduler; } + + virtual void scheduledActionBeginFrame() OVERRIDE { } + virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapIfPossible() OVERRIDE + { + // Only setNeedsRedraw the first time this is called + if (!m_numDraws) + m_scheduler->setNeedsRedraw(); + return FakeCCSchedulerClient::scheduledActionDrawAndSwapIfPossible(); + } + + virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapForced() OVERRIDE + { + ASSERT_NOT_REACHED(); + return CCScheduledActionDrawAndSwapResult(true, true); + } + + virtual void scheduledActionUpdateMoreResources(double) OVERRIDE { } + virtual void scheduledActionCommit() OVERRIDE { } + virtual void scheduledActionBeginContextRecreation() OVERRIDE { } + +protected: + CCScheduler* m_scheduler; +}; + +// Tests for two different situations: +// 1. the scheduler dropping setNeedsRedraw requests that happen inside +// a scheduledActionDrawAndSwap +// 2. the scheduler drawing twice inside a single tick +TEST(CCSchedulerTest, RequestRedrawInsideDraw) +{ + SchedulerClientThatSetNeedsDrawInsideDraw client; + RefPtr<FakeCCTimeSource> timeSource = adoptRef(new FakeCCTimeSource()); + OwnPtr<CCScheduler> scheduler = CCScheduler::create(&client, adoptPtr(new CCFrameRateController(timeSource))); + client.setScheduler(scheduler.get()); + scheduler->setCanBeginFrame(true); + scheduler->setVisible(true); + + scheduler->setNeedsRedraw(); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_TRUE(timeSource->active()); + EXPECT_EQ(0, client.numDraws()); + + timeSource->tick(); + EXPECT_EQ(1, client.numDraws()); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_TRUE(timeSource->active()); + + timeSource->tick(); + EXPECT_EQ(2, client.numDraws()); + EXPECT_FALSE(scheduler->redrawPending()); + EXPECT_FALSE(timeSource->active()); +} + +// Test that requesting redraw inside a failed draw doesn't lose the request. +TEST(CCSchedulerTest, RequestRedrawInsideFailedDraw) +{ + SchedulerClientThatSetNeedsDrawInsideDraw client; + RefPtr<FakeCCTimeSource> timeSource = adoptRef(new FakeCCTimeSource()); + OwnPtr<CCScheduler> scheduler = CCScheduler::create(&client, adoptPtr(new CCFrameRateController(timeSource))); + client.setScheduler(scheduler.get()); + scheduler->setCanBeginFrame(true); + scheduler->setVisible(true); + client.setDrawWillHappen(false); + + scheduler->setNeedsRedraw(); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_TRUE(timeSource->active()); + EXPECT_EQ(0, client.numDraws()); + + // Fail the draw. + timeSource->tick(); + EXPECT_EQ(1, client.numDraws()); + + // We have a commit pending and the draw failed, and we didn't lose the redraw request. + EXPECT_TRUE(scheduler->commitPending()); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_TRUE(timeSource->active()); + + // Fail the draw again. + timeSource->tick(); + EXPECT_EQ(2, client.numDraws()); + EXPECT_TRUE(scheduler->commitPending()); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_TRUE(timeSource->active()); + + // Draw successfully. + client.setDrawWillHappen(true); + timeSource->tick(); + EXPECT_EQ(3, client.numDraws()); + EXPECT_TRUE(scheduler->commitPending()); + EXPECT_FALSE(scheduler->redrawPending()); + EXPECT_FALSE(timeSource->active()); +} + +class SchedulerClientThatSetNeedsCommitInsideDraw : public FakeCCSchedulerClient { +public: + SchedulerClientThatSetNeedsCommitInsideDraw() + : m_scheduler(0) { } + + void setScheduler(CCScheduler* scheduler) { m_scheduler = scheduler; } + + virtual void scheduledActionBeginFrame() OVERRIDE { } + virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapIfPossible() OVERRIDE + { + // Only setNeedsCommit the first time this is called + if (!m_numDraws) + m_scheduler->setNeedsCommit(); + return FakeCCSchedulerClient::scheduledActionDrawAndSwapIfPossible(); + } + + virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapForced() OVERRIDE + { + ASSERT_NOT_REACHED(); + return CCScheduledActionDrawAndSwapResult(true, true); + } + + virtual void scheduledActionUpdateMoreResources(double) OVERRIDE { } + virtual void scheduledActionCommit() OVERRIDE { } + virtual void scheduledActionBeginContextRecreation() OVERRIDE { } + +protected: + CCScheduler* m_scheduler; +}; + +// Tests for the scheduler infinite-looping on setNeedsCommit requests that +// happen inside a scheduledActionDrawAndSwap +TEST(CCSchedulerTest, RequestCommitInsideDraw) +{ + SchedulerClientThatSetNeedsCommitInsideDraw client; + RefPtr<FakeCCTimeSource> timeSource = adoptRef(new FakeCCTimeSource()); + OwnPtr<CCScheduler> scheduler = CCScheduler::create(&client, adoptPtr(new CCFrameRateController(timeSource))); + client.setScheduler(scheduler.get()); + scheduler->setCanBeginFrame(true); + scheduler->setVisible(true); + + scheduler->setNeedsRedraw(); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_EQ(0, client.numDraws()); + EXPECT_TRUE(timeSource->active()); + + timeSource->tick(); + EXPECT_FALSE(timeSource->active()); + EXPECT_EQ(1, client.numDraws()); + EXPECT_TRUE(scheduler->commitPending()); + scheduler->beginFrameComplete(); + + timeSource->tick(); + EXPECT_EQ(2, client.numDraws()); + EXPECT_FALSE(timeSource->active()); + EXPECT_FALSE(scheduler->redrawPending()); +} + +// Tests that when a draw fails then the pending commit should not be dropped. +TEST(CCSchedulerTest, RequestCommitInsideFailedDraw) +{ + SchedulerClientThatSetNeedsDrawInsideDraw client; + RefPtr<FakeCCTimeSource> timeSource = adoptRef(new FakeCCTimeSource()); + OwnPtr<CCScheduler> scheduler = CCScheduler::create(&client, adoptPtr(new CCFrameRateController(timeSource))); + client.setScheduler(scheduler.get()); + scheduler->setCanBeginFrame(true); + scheduler->setVisible(true); + client.setDrawWillHappen(false); + + scheduler->setNeedsRedraw(); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_TRUE(timeSource->active()); + EXPECT_EQ(0, client.numDraws()); + + // Fail the draw. + timeSource->tick(); + EXPECT_EQ(1, client.numDraws()); + + // We have a commit pending and the draw failed, and we didn't lose the commit request. + EXPECT_TRUE(scheduler->commitPending()); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_TRUE(timeSource->active()); + + // Fail the draw again. + timeSource->tick(); + EXPECT_EQ(2, client.numDraws()); + EXPECT_TRUE(scheduler->commitPending()); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_TRUE(timeSource->active()); + + // Draw successfully. + client.setDrawWillHappen(true); + timeSource->tick(); + EXPECT_EQ(3, client.numDraws()); + EXPECT_TRUE(scheduler->commitPending()); + EXPECT_FALSE(scheduler->redrawPending()); + EXPECT_FALSE(timeSource->active()); +} + +TEST(CCSchedulerTest, NoBeginFrameWhenDrawFails) +{ + RefPtr<FakeCCTimeSource> timeSource = adoptRef(new FakeCCTimeSource()); + SchedulerClientThatSetNeedsCommitInsideDraw client; + OwnPtr<FakeCCFrameRateController> controller = adoptPtr(new FakeCCFrameRateController(timeSource)); + FakeCCFrameRateController* controllerPtr = controller.get(); + OwnPtr<CCScheduler> scheduler = CCScheduler::create(&client, controller.release()); + client.setScheduler(scheduler.get()); + scheduler->setCanBeginFrame(true); + scheduler->setVisible(true); + + EXPECT_EQ(0, controllerPtr->numFramesPending()); + + scheduler->setNeedsRedraw(); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_TRUE(timeSource->active()); + EXPECT_EQ(0, client.numDraws()); + + // Draw successfully, this starts a new frame. + timeSource->tick(); + EXPECT_EQ(1, client.numDraws()); + EXPECT_EQ(1, controllerPtr->numFramesPending()); + scheduler->didSwapBuffersComplete(); + EXPECT_EQ(0, controllerPtr->numFramesPending()); + + scheduler->setNeedsRedraw(); + EXPECT_TRUE(scheduler->redrawPending()); + EXPECT_TRUE(timeSource->active()); + + // Fail to draw, this should not start a frame. + client.setDrawWillHappen(false); + timeSource->tick(); + EXPECT_EQ(2, client.numDraws()); + EXPECT_EQ(0, controllerPtr->numFramesPending()); +} + +TEST(CCSchedulerTest, NoBeginFrameWhenSwapFailsDuringForcedCommit) +{ + RefPtr<FakeCCTimeSource> timeSource = adoptRef(new FakeCCTimeSource()); + FakeCCSchedulerClient client; + OwnPtr<FakeCCFrameRateController> controller = adoptPtr(new FakeCCFrameRateController(timeSource)); + FakeCCFrameRateController* controllerPtr = controller.get(); + OwnPtr<CCScheduler> scheduler = CCScheduler::create(&client, controller.release()); + + EXPECT_EQ(0, controllerPtr->numFramesPending()); + + // Tell the client that it will fail to swap. + client.setDrawWillHappen(true); + client.setSwapWillHappenIfDrawHappens(false); + + // Get the compositor to do a scheduledActionDrawAndSwapForced. + scheduler->setNeedsRedraw(); + scheduler->setNeedsForcedRedraw(); + EXPECT_TRUE(client.hasAction("scheduledActionDrawAndSwapForced")); + + // We should not have told the frame rate controller that we began a frame. + EXPECT_EQ(0, controllerPtr->numFramesPending()); +} + +} diff --git a/cc/CCScopedTexture.cpp b/cc/CCScopedTexture.cpp new file mode 100644 index 0000000..b032680 --- /dev/null +++ b/cc/CCScopedTexture.cpp @@ -0,0 +1,51 @@ +// Copyright 2012 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 "CCScopedTexture.h" + +namespace WebCore { + +CCScopedTexture::CCScopedTexture(CCResourceProvider* resourceProvider) + : m_resourceProvider(resourceProvider) +{ + ASSERT(m_resourceProvider); +} + +CCScopedTexture::~CCScopedTexture() +{ + free(); +} + +bool CCScopedTexture::allocate(int pool, const IntSize& size, GC3Denum format, CCResourceProvider::TextureUsageHint hint) +{ + ASSERT(!id()); + ASSERT(!size.isEmpty()); + + setDimensions(size, format); + setId(m_resourceProvider->createResource(pool, size, format, hint)); + +#if !ASSERT_DISABLED + m_allocateThreadIdentifier = WTF::currentThread(); +#endif + + return id(); +} + +void CCScopedTexture::free() +{ + if (id()) { + ASSERT(m_allocateThreadIdentifier == WTF::currentThread()); + m_resourceProvider->deleteResource(id()); + } + setId(0); +} + +void CCScopedTexture::leak() +{ + setId(0); +} + +} diff --git a/cc/CCScopedTexture.h b/cc/CCScopedTexture.h new file mode 100644 index 0000000..c82b8bc --- /dev/null +++ b/cc/CCScopedTexture.h @@ -0,0 +1,44 @@ +// Copyright 2012 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. + +#ifndef CCScopedTexture_h +#define CCScopedTexture_h + +#include "CCTexture.h" + +#if !ASSERT_DISABLED +#include <wtf/MainThread.h> +#endif + +namespace WebCore { + +class CCScopedTexture : protected CCTexture { + WTF_MAKE_NONCOPYABLE(CCScopedTexture); +public: + static PassOwnPtr<CCScopedTexture> create(CCResourceProvider* resourceProvider) { return adoptPtr(new CCScopedTexture(resourceProvider)); } + virtual ~CCScopedTexture(); + + using CCTexture::id; + using CCTexture::size; + using CCTexture::format; + using CCTexture::bytes; + + bool allocate(int pool, const IntSize&, GC3Denum format, CCResourceProvider::TextureUsageHint); + void free(); + void leak(); + +protected: + explicit CCScopedTexture(CCResourceProvider*); + +private: + CCResourceProvider* m_resourceProvider; + +#if !ASSERT_DISABLED + ThreadIdentifier m_allocateThreadIdentifier; +#endif +}; + +} + +#endif diff --git a/cc/CCScopedTextureTest.cpp b/cc/CCScopedTextureTest.cpp new file mode 100644 index 0000000..b39efca --- /dev/null +++ b/cc/CCScopedTextureTest.cpp @@ -0,0 +1,108 @@ +// Copyright 2012 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 "CCScopedTexture.h" + +#include "CCRenderer.h" +#include "CCSingleThreadProxy.h" // For DebugScopedSetImplThread +#include "CCTiledLayerTestCommon.h" +#include "FakeCCGraphicsContext.h" +#include "GraphicsContext3D.h" +#include <gtest/gtest.h> + +using namespace WebCore; +using namespace WebKit; +using namespace WebKitTests; + +namespace { + +TEST(CCScopedTextureTest, NewScopedTexture) +{ + OwnPtr<CCGraphicsContext> context(createFakeCCGraphicsContext()); + DebugScopedSetImplThread implThread; + OwnPtr<CCResourceProvider> resourceProvider(CCResourceProvider::create(context.get())); + OwnPtr<CCScopedTexture> texture = CCScopedTexture::create(resourceProvider.get()); + + // New scoped textures do not hold a texture yet. + EXPECT_EQ(0u, texture->id()); + + // New scoped textures do not have a size yet. + EXPECT_EQ(IntSize(), texture->size()); + EXPECT_EQ(0u, texture->bytes()); +} + +TEST(CCScopedTextureTest, CreateScopedTexture) +{ + OwnPtr<CCGraphicsContext> context(createFakeCCGraphicsContext()); + DebugScopedSetImplThread implThread; + OwnPtr<CCResourceProvider> resourceProvider(CCResourceProvider::create(context.get())); + OwnPtr<CCScopedTexture> texture = CCScopedTexture::create(resourceProvider.get()); + texture->allocate(CCRenderer::ImplPool, IntSize(30, 30), GraphicsContext3D::RGBA, CCResourceProvider::TextureUsageAny); + + // The texture has an allocated byte-size now. + size_t expectedBytes = 30 * 30 * 4; + EXPECT_EQ(expectedBytes, texture->bytes()); + + EXPECT_LT(0u, texture->id()); + EXPECT_EQ(GraphicsContext3D::RGBA, texture->format()); + EXPECT_EQ(IntSize(30, 30), texture->size()); +} + +TEST(CCScopedTextureTest, ScopedTextureIsDeleted) +{ + OwnPtr<CCGraphicsContext> context(createFakeCCGraphicsContext()); + DebugScopedSetImplThread implThread; + OwnPtr<CCResourceProvider> resourceProvider(CCResourceProvider::create(context.get())); + + { + OwnPtr<CCScopedTexture> texture = CCScopedTexture::create(resourceProvider.get()); + + EXPECT_EQ(0u, resourceProvider->numResources()); + texture->allocate(CCRenderer::ImplPool, IntSize(30, 30), GraphicsContext3D::RGBA, CCResourceProvider::TextureUsageAny); + EXPECT_LT(0u, texture->id()); + EXPECT_EQ(1u, resourceProvider->numResources()); + } + + EXPECT_EQ(0u, resourceProvider->numResources()); + + { + OwnPtr<CCScopedTexture> texture = CCScopedTexture::create(resourceProvider.get()); + EXPECT_EQ(0u, resourceProvider->numResources()); + texture->allocate(CCRenderer::ImplPool, IntSize(30, 30), GraphicsContext3D::RGBA, CCResourceProvider::TextureUsageAny); + EXPECT_LT(0u, texture->id()); + EXPECT_EQ(1u, resourceProvider->numResources()); + texture->free(); + EXPECT_EQ(0u, resourceProvider->numResources()); + } +} + +TEST(CCScopedTextureTest, LeakScopedTexture) +{ + OwnPtr<CCGraphicsContext> context(createFakeCCGraphicsContext()); + DebugScopedSetImplThread implThread; + OwnPtr<CCResourceProvider> resourceProvider(CCResourceProvider::create(context.get())); + + { + OwnPtr<CCScopedTexture> texture = CCScopedTexture::create(resourceProvider.get()); + + EXPECT_EQ(0u, resourceProvider->numResources()); + texture->allocate(CCRenderer::ImplPool, IntSize(30, 30), GraphicsContext3D::RGBA, CCResourceProvider::TextureUsageAny); + EXPECT_LT(0u, texture->id()); + EXPECT_EQ(1u, resourceProvider->numResources()); + + texture->leak(); + EXPECT_EQ(0u, texture->id()); + EXPECT_EQ(1u, resourceProvider->numResources()); + + texture->free(); + EXPECT_EQ(0u, texture->id()); + EXPECT_EQ(1u, resourceProvider->numResources()); + } + + EXPECT_EQ(1u, resourceProvider->numResources()); +} + +} diff --git a/cc/CCScopedThreadProxy.h b/cc/CCScopedThreadProxy.h new file mode 100644 index 0000000..e30587c --- /dev/null +++ b/cc/CCScopedThreadProxy.h @@ -0,0 +1,71 @@ +// 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. + +#ifndef CCScopedThreadProxy_h +#define CCScopedThreadProxy_h + +#include "CCThreadTask.h" +#include <wtf/ThreadSafeRefCounted.h> + +namespace WebCore { + +// This class is a proxy used to post tasks to an target thread from any other thread. The proxy may be shut down at +// any point from the target thread after which no more tasks posted to the proxy will run. In other words, all +// tasks posted via a proxy are scoped to the lifecycle of the proxy. Use this when posting tasks to an object that +// might die with tasks in flight. +// +// The proxy must be created and shut down from the target thread, tasks may be posted from any thread. +// +// Implementation note: Unlike ScopedRunnableMethodFactory in Chromium, pending tasks are not cancelled by actually +// destroying the proxy. Instead each pending task holds a reference to the proxy to avoid maintaining an explicit +// list of outstanding tasks. +class CCScopedThreadProxy : public ThreadSafeRefCounted<CCScopedThreadProxy> { +public: + static PassRefPtr<CCScopedThreadProxy> create(CCThread* targetThread) + { + ASSERT(currentThread() == targetThread->threadID()); + return adoptRef(new CCScopedThreadProxy(targetThread)); + } + + // Can be called from any thread. Posts a task to the target thread that runs unless + // shutdown() is called before it runs. + void postTask(PassOwnPtr<CCThread::Task> task) + { + ref(); + m_targetThread->postTask(createCCThreadTask(this, &CCScopedThreadProxy::runTaskIfNotShutdown, task)); + } + + void shutdown() + { + ASSERT(currentThread() == m_targetThread->threadID()); + ASSERT(!m_shutdown); + m_shutdown = true; + } + +private: + explicit CCScopedThreadProxy(CCThread* targetThread) + : m_targetThread(targetThread) + , m_shutdown(false) { } + + void runTaskIfNotShutdown(PassOwnPtr<CCThread::Task> popTask) + { + OwnPtr<CCThread::Task> task = popTask; + // If our shutdown flag is set, it's possible that m_targetThread has already been destroyed so don't + // touch it. + if (m_shutdown) { + deref(); + return; + } + ASSERT(currentThread() == m_targetThread->threadID()); + task->performTask(); + deref(); + } + + CCThread* m_targetThread; + bool m_shutdown; // Only accessed on the target thread +}; + +} + +#endif diff --git a/cc/CCScrollbarAnimationController.cpp b/cc/CCScrollbarAnimationController.cpp new file mode 100644 index 0000000..fa5ef0d --- /dev/null +++ b/cc/CCScrollbarAnimationController.cpp @@ -0,0 +1,92 @@ +// Copyright 2012 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 "CCScrollbarAnimationController.h" + +#include "CCScrollbarLayerImpl.h" +#include <wtf/CurrentTime.h> + +#if OS(ANDROID) +#include "CCScrollbarAnimationControllerLinearFade.h" +#endif + +namespace WebCore { + +#if OS(ANDROID) +PassOwnPtr<CCScrollbarAnimationController> CCScrollbarAnimationController::create(CCLayerImpl* scrollLayer) +{ + static const double fadeoutDelay = 0.3; + static const double fadeoutLength = 0.3; + return CCScrollbarAnimationControllerLinearFade::create(scrollLayer, fadeoutDelay, fadeoutLength); +} +#else +PassOwnPtr<CCScrollbarAnimationController> CCScrollbarAnimationController::create(CCLayerImpl* scrollLayer) +{ + return adoptPtr(new CCScrollbarAnimationController(scrollLayer)); +} +#endif + +CCScrollbarAnimationController::CCScrollbarAnimationController(CCLayerImpl* scrollLayer) + : m_horizontalScrollbarLayer(0) + , m_verticalScrollbarLayer(0) +{ + CCScrollbarAnimationController::updateScrollOffsetAtTime(scrollLayer, 0); +} + +CCScrollbarAnimationController::~CCScrollbarAnimationController() +{ +} + +void CCScrollbarAnimationController::didPinchGestureBegin() +{ + didPinchGestureBeginAtTime(monotonicallyIncreasingTime()); +} + +void CCScrollbarAnimationController::didPinchGestureUpdate() +{ + didPinchGestureUpdateAtTime(monotonicallyIncreasingTime()); +} + +void CCScrollbarAnimationController::didPinchGestureEnd() +{ + didPinchGestureEndAtTime(monotonicallyIncreasingTime()); +} + +void CCScrollbarAnimationController::updateScrollOffset(CCLayerImpl* scrollLayer) +{ + updateScrollOffsetAtTime(scrollLayer, monotonicallyIncreasingTime()); +} + +IntSize CCScrollbarAnimationController::getScrollLayerBounds(const CCLayerImpl* scrollLayer) +{ + if (!scrollLayer->children().size()) + return IntSize(); + // Copy & paste from CCLayerTreeHostImpl... + // FIXME: Hardcoding the first child here is weird. Think of + // a cleaner way to get the contentBounds on the Impl side. + return scrollLayer->children()[0]->bounds(); +} + +void CCScrollbarAnimationController::updateScrollOffsetAtTime(CCLayerImpl* scrollLayer, double) +{ + m_currentPos = scrollLayer->scrollPosition() + scrollLayer->scrollDelta(); + m_totalSize = getScrollLayerBounds(scrollLayer); + m_maximum = scrollLayer->maxScrollPosition(); + + if (m_horizontalScrollbarLayer) { + m_horizontalScrollbarLayer->setCurrentPos(m_currentPos.x()); + m_horizontalScrollbarLayer->setTotalSize(m_totalSize.width()); + m_horizontalScrollbarLayer->setMaximum(m_maximum.width()); + } + + if (m_verticalScrollbarLayer) { + m_verticalScrollbarLayer->setCurrentPos(m_currentPos.y()); + m_verticalScrollbarLayer->setTotalSize(m_totalSize.height()); + m_verticalScrollbarLayer->setMaximum(m_maximum.height()); + } +} + +} // namespace WebCore diff --git a/cc/CCScrollbarAnimationController.h b/cc/CCScrollbarAnimationController.h new file mode 100644 index 0000000..256b463 --- /dev/null +++ b/cc/CCScrollbarAnimationController.h @@ -0,0 +1,64 @@ +// Copyright 2012 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. + +#ifndef CCScrollbarAnimationController_h +#define CCScrollbarAnimationController_h + +#include "FloatPoint.h" +#include "IntSize.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCLayerImpl; +class CCScrollbarLayerImpl; + +// This abstract class represents the compositor-side analogy of ScrollbarAnimator. +// Individual platforms should subclass it to provide specialized implementation. +class CCScrollbarAnimationController { +public: + // Implemented by subclass. + static PassOwnPtr<CCScrollbarAnimationController> create(CCLayerImpl* scrollLayer); + + virtual ~CCScrollbarAnimationController(); + + virtual bool animate(double monotonicTime) { return false; } + void didPinchGestureBegin(); + void didPinchGestureUpdate(); + void didPinchGestureEnd(); + void updateScrollOffset(CCLayerImpl* scrollLayer); + + void setHorizontalScrollbarLayer(CCScrollbarLayerImpl* layer) { m_horizontalScrollbarLayer = layer; } + CCScrollbarLayerImpl* horizontalScrollbarLayer() const { return m_horizontalScrollbarLayer; } + + void setVerticalScrollbarLayer(CCScrollbarLayerImpl* layer) { m_verticalScrollbarLayer = layer; } + CCScrollbarLayerImpl* verticalScrollbarLayer() const { return m_verticalScrollbarLayer; } + + FloatPoint currentPos() const { return m_currentPos; } + IntSize totalSize() const { return m_totalSize; } + IntSize maximum() const { return m_maximum; } + + virtual void didPinchGestureBeginAtTime(double monotonicTime) { } + virtual void didPinchGestureUpdateAtTime(double monotonicTime) { } + virtual void didPinchGestureEndAtTime(double monotonicTime) { } + virtual void updateScrollOffsetAtTime(CCLayerImpl* scrollLayer, double monotonicTime); + +protected: + explicit CCScrollbarAnimationController(CCLayerImpl* scrollLayer); + +private: + static IntSize getScrollLayerBounds(const CCLayerImpl*); + + // Beware of dangling pointer. Always update these during tree synchronization. + CCScrollbarLayerImpl* m_horizontalScrollbarLayer; + CCScrollbarLayerImpl* m_verticalScrollbarLayer; + + FloatPoint m_currentPos; + IntSize m_totalSize; + IntSize m_maximum; +}; + +} // namespace WebCore + +#endif // CCScrollbarAnimationController_h diff --git a/cc/CCScrollbarAnimationControllerLinearFade.cpp b/cc/CCScrollbarAnimationControllerLinearFade.cpp new file mode 100644 index 0000000..0207fb1 --- /dev/null +++ b/cc/CCScrollbarAnimationControllerLinearFade.cpp @@ -0,0 +1,78 @@ +// Copyright 2012 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 "CCScrollbarAnimationControllerLinearFade.h" + +#include "CCScrollbarLayerImpl.h" + +namespace WebCore { + +PassOwnPtr<CCScrollbarAnimationControllerLinearFade> CCScrollbarAnimationControllerLinearFade::create(CCLayerImpl* scrollLayer, double fadeoutDelay, double fadeoutLength) +{ + return adoptPtr(new CCScrollbarAnimationControllerLinearFade(scrollLayer, fadeoutDelay, fadeoutLength)); +} + +CCScrollbarAnimationControllerLinearFade::CCScrollbarAnimationControllerLinearFade(CCLayerImpl* scrollLayer, double fadeoutDelay, double fadeoutLength) + : CCScrollbarAnimationController(scrollLayer) + , m_lastAwakenTime(-100000000) // arbitrary invalid timestamp + , m_pinchGestureInEffect(false) + , m_fadeoutDelay(fadeoutDelay) + , m_fadeoutLength(fadeoutLength) +{ +} + +CCScrollbarAnimationControllerLinearFade::~CCScrollbarAnimationControllerLinearFade() +{ +} + +bool CCScrollbarAnimationControllerLinearFade::animate(double monotonicTime) +{ + float opacity = opacityAtTime(monotonicTime); + if (horizontalScrollbarLayer()) + horizontalScrollbarLayer()->setOpacity(opacity); + if (verticalScrollbarLayer()) + verticalScrollbarLayer()->setOpacity(opacity); + return opacity; +} + +void CCScrollbarAnimationControllerLinearFade::didPinchGestureUpdateAtTime(double) +{ + m_pinchGestureInEffect = true; +} + +void CCScrollbarAnimationControllerLinearFade::didPinchGestureEndAtTime(double monotonicTime) +{ + m_pinchGestureInEffect = false; + m_lastAwakenTime = monotonicTime; +} + +void CCScrollbarAnimationControllerLinearFade::updateScrollOffsetAtTime(CCLayerImpl* scrollLayer, double monotonicTime) +{ + FloatPoint previousPos = currentPos(); + CCScrollbarAnimationController::updateScrollOffsetAtTime(scrollLayer, monotonicTime); + + if (previousPos == currentPos()) + return; + + m_lastAwakenTime = monotonicTime; +} + +float CCScrollbarAnimationControllerLinearFade::opacityAtTime(double monotonicTime) +{ + if (m_pinchGestureInEffect) + return 1; + + double delta = monotonicTime - m_lastAwakenTime; + + if (delta <= m_fadeoutDelay) + return 1; + if (delta < m_fadeoutDelay + m_fadeoutLength) + return (m_fadeoutDelay + m_fadeoutLength - delta) / m_fadeoutLength; + return 0; +} + +} // namespace WebCore + diff --git a/cc/CCScrollbarAnimationControllerLinearFade.h b/cc/CCScrollbarAnimationControllerLinearFade.h new file mode 100644 index 0000000..76bf7d6 --- /dev/null +++ b/cc/CCScrollbarAnimationControllerLinearFade.h @@ -0,0 +1,39 @@ +// Copyright 2012 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. + +#ifndef CCScrollbarAnimationControllerLinearFade_h +#define CCScrollbarAnimationControllerLinearFade_h + +#include "CCScrollbarAnimationController.h" + +namespace WebCore { + +class CCScrollbarAnimationControllerLinearFade : public CCScrollbarAnimationController { +public: + static PassOwnPtr<CCScrollbarAnimationControllerLinearFade> create(CCLayerImpl* scrollLayer, double fadeoutDelay, double fadeoutLength); + + virtual ~CCScrollbarAnimationControllerLinearFade(); + + virtual bool animate(double monotonicTime) OVERRIDE; + + virtual void didPinchGestureUpdateAtTime(double monotonicTime) OVERRIDE; + virtual void didPinchGestureEndAtTime(double monotonicTime) OVERRIDE; + virtual void updateScrollOffsetAtTime(CCLayerImpl* scrollLayer, double monotonicTime) OVERRIDE; + +protected: + CCScrollbarAnimationControllerLinearFade(CCLayerImpl* scrollLayer, double fadeoutDelay, double fadeoutLength); + +private: + float opacityAtTime(double monotonicTime); + + double m_lastAwakenTime; + bool m_pinchGestureInEffect; + + double m_fadeoutDelay; + double m_fadeoutLength; +}; + +} // namespace WebCore + +#endif // CCScrollbarAnimationControllerLinearFade_h diff --git a/cc/CCScrollbarAnimationControllerLinearFadeTest.cpp b/cc/CCScrollbarAnimationControllerLinearFadeTest.cpp new file mode 100644 index 0000000..9294c65 --- /dev/null +++ b/cc/CCScrollbarAnimationControllerLinearFadeTest.cpp @@ -0,0 +1,120 @@ +// Copyright 2012 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 "CCScrollbarAnimationControllerLinearFade.h" + +#include "CCScrollbarLayerImpl.h" +#include "CCSingleThreadProxy.h" +#include <gtest/gtest.h> +#include <wtf/OwnPtr.h> + +using namespace WebCore; + +namespace { + +class CCScrollbarAnimationControllerLinearFadeTest : public testing::Test { +protected: + virtual void SetUp() + { + m_scrollLayer = CCLayerImpl::create(1); + m_scrollLayer->addChild(CCLayerImpl::create(2)); + m_contentLayer = m_scrollLayer->children()[0].get(); + m_scrollbarLayer = CCScrollbarLayerImpl::create(3); + + m_scrollLayer->setMaxScrollPosition(IntSize(50, 50)); + m_contentLayer->setBounds(IntSize(50, 50)); + + m_scrollbarController = CCScrollbarAnimationControllerLinearFade::create(m_scrollLayer.get(), 2, 3); + m_scrollbarController->setHorizontalScrollbarLayer(m_scrollbarLayer.get()); + } + + DebugScopedSetImplThread implThread; + + OwnPtr<CCScrollbarAnimationControllerLinearFade> m_scrollbarController; + OwnPtr<CCLayerImpl> m_scrollLayer; + CCLayerImpl* m_contentLayer; + OwnPtr<CCScrollbarLayerImpl> m_scrollbarLayer; + +}; + +TEST_F(CCScrollbarAnimationControllerLinearFadeTest, verifyHiddenInBegin) +{ + m_scrollbarController->animate(0); + EXPECT_FLOAT_EQ(0, m_scrollbarLayer->opacity()); + m_scrollbarController->updateScrollOffsetAtTime(m_scrollLayer.get(), 0); + m_scrollbarController->animate(0); + EXPECT_FLOAT_EQ(0, m_scrollbarLayer->opacity()); +} + +TEST_F(CCScrollbarAnimationControllerLinearFadeTest, verifyAwakenByScroll) +{ + m_scrollLayer->setScrollDelta(IntSize(1, 1)); + m_scrollbarController->updateScrollOffsetAtTime(m_scrollLayer.get(), 0); + m_scrollbarController->animate(0); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(1); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollLayer->setScrollDelta(IntSize(2, 2)); + m_scrollbarController->updateScrollOffsetAtTime(m_scrollLayer.get(), 1); + m_scrollbarController->animate(2); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(3); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(4); + // Note that we use 3.0f to avoid "argument is truncated from 'double' to + // 'float'" warnings on Windows. + EXPECT_FLOAT_EQ(2 / 3.0f, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(5); + EXPECT_FLOAT_EQ(1 / 3.0f, m_scrollbarLayer->opacity()); + m_scrollLayer->setScrollDelta(IntSize(3, 3)); + m_scrollbarController->updateScrollOffsetAtTime(m_scrollLayer.get(), 5); + m_scrollbarController->animate(6); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(7); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(8); + EXPECT_FLOAT_EQ(2 / 3.0f, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(9); + EXPECT_FLOAT_EQ(1 / 3.0f, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(10); + EXPECT_FLOAT_EQ(0, m_scrollbarLayer->opacity()); +} + +TEST_F(CCScrollbarAnimationControllerLinearFadeTest, verifyForceAwakenByPinch) +{ + m_scrollbarController->didPinchGestureBeginAtTime(0); + m_scrollbarController->didPinchGestureUpdateAtTime(0); + m_scrollbarController->animate(0); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(1); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollLayer->setScrollDelta(IntSize(1, 1)); + m_scrollbarController->updateScrollOffsetAtTime(m_scrollLayer.get(), 1); + m_scrollbarController->animate(2); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(3); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(4); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(5); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(6); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->didPinchGestureEndAtTime(6); + m_scrollbarController->animate(7); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(8); + EXPECT_FLOAT_EQ(1, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(9); + EXPECT_FLOAT_EQ(2 / 3.0f, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(10); + EXPECT_FLOAT_EQ(1 / 3.0f, m_scrollbarLayer->opacity()); + m_scrollbarController->animate(11); + EXPECT_FLOAT_EQ(0, m_scrollbarLayer->opacity()); + +} + +} diff --git a/cc/CCScrollbarLayerImpl.cpp b/cc/CCScrollbarLayerImpl.cpp new file mode 100644 index 0000000..b81844a --- /dev/null +++ b/cc/CCScrollbarLayerImpl.cpp @@ -0,0 +1,193 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCScrollbarLayerImpl.h" + +#include "CCQuadSink.h" +#include "CCScrollbarAnimationController.h" +#include "CCTextureDrawQuad.h" + +using WebKit::WebRect; +using WebKit::WebScrollbar; + +namespace WebCore { + +PassOwnPtr<CCScrollbarLayerImpl> CCScrollbarLayerImpl::create(int id) +{ + return adoptPtr(new CCScrollbarLayerImpl(id)); +} + +CCScrollbarLayerImpl::CCScrollbarLayerImpl(int id) + : CCLayerImpl(id) + , m_scrollbar(this) + , m_backTrackResourceId(0) + , m_foreTrackResourceId(0) + , m_thumbResourceId(0) + , m_scrollbarOverlayStyle(WebScrollbar::ScrollbarOverlayStyleDefault) + , m_orientation(WebScrollbar::Horizontal) + , m_controlSize(WebScrollbar::RegularScrollbar) + , m_pressedPart(WebScrollbar::NoPart) + , m_hoveredPart(WebScrollbar::NoPart) + , m_isScrollableAreaActive(false) + , m_isScrollViewScrollbar(false) + , m_enabled(false) + , m_isCustomScrollbar(false) + , m_isOverlayScrollbar(false) +{ +} + +void CCScrollbarLayerImpl::setScrollbarGeometry(PassOwnPtr<WebKit::WebScrollbarThemeGeometry> geometry) +{ + m_geometry = geometry; +} + +void CCScrollbarLayerImpl::setScrollbarData(const WebScrollbar* scrollbar) +{ + m_scrollbarOverlayStyle = scrollbar->scrollbarOverlayStyle(); + m_orientation = scrollbar->orientation(); + m_controlSize = scrollbar->controlSize(); + m_pressedPart = scrollbar->pressedPart(); + m_hoveredPart = scrollbar->hoveredPart(); + m_isScrollableAreaActive = scrollbar->isScrollableAreaActive(); + m_isScrollViewScrollbar = scrollbar->isScrollViewScrollbar(); + m_enabled = scrollbar->enabled(); + m_isCustomScrollbar = scrollbar->isCustomScrollbar(); + m_isOverlayScrollbar = scrollbar->isOverlay(); + + scrollbar->getTickmarks(m_tickmarks); +} + +static FloatRect toUVRect(const WebRect& r, const IntRect& bounds) +{ + return FloatRect(static_cast<float>(r.x) / bounds.width(), static_cast<float>(r.y) / bounds.height(), + static_cast<float>(r.width) / bounds.width(), static_cast<float>(r.height) / bounds.height()); +} + +void CCScrollbarLayerImpl::appendQuads(CCQuadSink& quadSink, bool&) +{ + bool premultipledAlpha = false; + bool flipped = false; + FloatRect uvRect(0, 0, 1, 1); + IntRect boundsRect(IntPoint(), contentBounds()); + + CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState()); + appendDebugBorderQuad(quadSink, sharedQuadState); + + WebRect thumbRect, backTrackRect, foreTrackRect; + m_geometry->splitTrack(&m_scrollbar, m_geometry->trackRect(&m_scrollbar), backTrackRect, thumbRect, foreTrackRect); + if (!m_geometry->hasThumb(&m_scrollbar)) + thumbRect = WebRect(); + + if (m_thumbResourceId && !thumbRect.isEmpty()) { + OwnPtr<CCTextureDrawQuad> quad = CCTextureDrawQuad::create(sharedQuadState, IntRect(thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height), m_thumbResourceId, premultipledAlpha, uvRect, flipped); + quad->setNeedsBlending(); + quadSink.append(quad.release()); + } + + if (!m_backTrackResourceId) + return; + + // We only paint the track in two parts if we were given a texture for the forward track part. + if (m_foreTrackResourceId && !foreTrackRect.isEmpty()) + quadSink.append(CCTextureDrawQuad::create(sharedQuadState, IntRect(foreTrackRect.x, foreTrackRect.y, foreTrackRect.width, foreTrackRect.height), m_foreTrackResourceId, premultipledAlpha, toUVRect(foreTrackRect, boundsRect), flipped)); + + // Order matters here: since the back track texture is being drawn to the entire contents rect, we must append it after the thumb and + // fore track quads. The back track texture contains (and displays) the buttons. + if (!boundsRect.isEmpty()) + quadSink.append(CCTextureDrawQuad::create(sharedQuadState, IntRect(boundsRect), m_backTrackResourceId, premultipledAlpha, uvRect, flipped)); +} + +void CCScrollbarLayerImpl::didLoseContext() +{ + m_backTrackResourceId = 0; + m_foreTrackResourceId = 0; + m_thumbResourceId = 0; +} + +bool CCScrollbarLayerImpl::CCScrollbar::isOverlay() const +{ + return m_owner->m_isOverlayScrollbar; +} + +int CCScrollbarLayerImpl::CCScrollbar::value() const +{ + return m_owner->m_currentPos; +} + +WebKit::WebPoint CCScrollbarLayerImpl::CCScrollbar::location() const +{ + return WebKit::WebPoint(); +} + +WebKit::WebSize CCScrollbarLayerImpl::CCScrollbar::size() const +{ + return WebKit::WebSize(m_owner->contentBounds().width(), m_owner->contentBounds().height()); +} + +bool CCScrollbarLayerImpl::CCScrollbar::enabled() const +{ + return m_owner->m_enabled; +} + +int CCScrollbarLayerImpl::CCScrollbar::maximum() const +{ + return m_owner->m_maximum; +} + +int CCScrollbarLayerImpl::CCScrollbar::totalSize() const +{ + return m_owner->m_totalSize; +} + +bool CCScrollbarLayerImpl::CCScrollbar::isScrollViewScrollbar() const +{ + return m_owner->m_isScrollViewScrollbar; +} + +bool CCScrollbarLayerImpl::CCScrollbar::isScrollableAreaActive() const +{ + return m_owner->m_isScrollableAreaActive; +} + +void CCScrollbarLayerImpl::CCScrollbar::getTickmarks(WebKit::WebVector<WebRect>& tickmarks) const +{ + tickmarks = m_owner->m_tickmarks; +} + +WebScrollbar::ScrollbarControlSize CCScrollbarLayerImpl::CCScrollbar::controlSize() const +{ + return m_owner->m_controlSize; +} + +WebScrollbar::ScrollbarPart CCScrollbarLayerImpl::CCScrollbar::pressedPart() const +{ + return m_owner->m_pressedPart; +} + +WebScrollbar::ScrollbarPart CCScrollbarLayerImpl::CCScrollbar::hoveredPart() const +{ + return m_owner->m_hoveredPart; +} + +WebScrollbar::ScrollbarOverlayStyle CCScrollbarLayerImpl::CCScrollbar::scrollbarOverlayStyle() const +{ + return m_owner->m_scrollbarOverlayStyle; +} + +WebScrollbar::Orientation CCScrollbarLayerImpl::CCScrollbar::orientation() const +{ + return m_owner->m_orientation; +} + +bool CCScrollbarLayerImpl::CCScrollbar::isCustomScrollbar() const +{ + return m_owner->m_isCustomScrollbar; +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCScrollbarLayerImpl.h b/cc/CCScrollbarLayerImpl.h new file mode 100644 index 0000000..ebe3faa --- /dev/null +++ b/cc/CCScrollbarLayerImpl.h @@ -0,0 +1,109 @@ +// Copyright 2012 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. + +#ifndef CCScrollbarLayerImpl_h +#define CCScrollbarLayerImpl_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCLayerImpl.h" +#include <public/WebRect.h> +#include <public/WebScrollbar.h> +#include <public/WebScrollbarThemeGeometry.h> +#include <public/WebVector.h> + +namespace WebCore { + +class ScrollView; + +class CCScrollbarLayerImpl : public CCLayerImpl { +public: + static PassOwnPtr<CCScrollbarLayerImpl> create(int id); + + WebKit::WebScrollbarThemeGeometry* scrollbarGeometry() const { return m_geometry.get(); } + void setScrollbarGeometry(PassOwnPtr<WebKit::WebScrollbarThemeGeometry>); + void setScrollbarData(const WebKit::WebScrollbar*); + + void setBackTrackResourceId(CCResourceProvider::ResourceId id) { m_backTrackResourceId = id; } + void setForeTrackResourceId(CCResourceProvider::ResourceId id) { m_foreTrackResourceId = id; } + void setThumbResourceId(CCResourceProvider::ResourceId id) { m_thumbResourceId = id; } + + float currentPos() const { return m_currentPos; } + void setCurrentPos(float currentPos) { m_currentPos = currentPos; } + + int totalSize() const { return m_totalSize; } + void setTotalSize(int totalSize) { m_totalSize = totalSize; } + + int maximum() const { return m_maximum; } + void setMaximum(int maximum) { m_maximum = maximum; } + + WebKit::WebScrollbar::Orientation orientation() const { return m_orientation; } + + virtual void appendQuads(CCQuadSink&, bool& hadMissingTiles) OVERRIDE; + + virtual void didLoseContext() OVERRIDE; + +protected: + explicit CCScrollbarLayerImpl(int id); + +private: + // nested class only to avoid namespace problem + class CCScrollbar : public WebKit::WebScrollbar { + public: + explicit CCScrollbar(CCScrollbarLayerImpl* owner) : m_owner(owner) { } + + // WebScrollbar implementation + virtual bool isOverlay() const; + virtual int value() const; + virtual WebKit::WebPoint location() const; + virtual WebKit::WebSize size() const; + virtual bool enabled() const; + virtual int maximum() const; + virtual int totalSize() const; + virtual bool isScrollViewScrollbar() const; + virtual bool isScrollableAreaActive() const; + virtual void getTickmarks(WebKit::WebVector<WebKit::WebRect>& tickmarks) const; + virtual WebScrollbar::ScrollbarControlSize controlSize() const; + virtual WebScrollbar::ScrollbarPart pressedPart() const; + virtual WebScrollbar::ScrollbarPart hoveredPart() const; + virtual WebScrollbar::ScrollbarOverlayStyle scrollbarOverlayStyle() const; + virtual WebScrollbar::Orientation orientation() const; + virtual bool isCustomScrollbar() const; + + private: + CCScrollbarLayerImpl* m_owner; + + }; + + CCScrollbar m_scrollbar; + + CCResourceProvider::ResourceId m_backTrackResourceId; + CCResourceProvider::ResourceId m_foreTrackResourceId; + CCResourceProvider::ResourceId m_thumbResourceId; + + OwnPtr<WebKit::WebScrollbarThemeGeometry> m_geometry; + + // Data to implement CCScrollbar + WebKit::WebScrollbar::ScrollbarOverlayStyle m_scrollbarOverlayStyle; + WebKit::WebVector<WebKit::WebRect> m_tickmarks; + WebKit::WebScrollbar::Orientation m_orientation; + WebKit::WebScrollbar::ScrollbarControlSize m_controlSize; + WebKit::WebScrollbar::ScrollbarPart m_pressedPart; + WebKit::WebScrollbar::ScrollbarPart m_hoveredPart; + + float m_currentPos; + int m_totalSize; + int m_maximum; + + bool m_isScrollableAreaActive; + bool m_isScrollViewScrollbar; + bool m_enabled; + bool m_isCustomScrollbar; + bool m_isOverlayScrollbar; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/CCSettings.cpp b/cc/CCSettings.cpp new file mode 100644 index 0000000..32c023c --- /dev/null +++ b/cc/CCSettings.cpp @@ -0,0 +1,33 @@ +// Copyright 2012 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 "CCSettings.h" + +namespace { +static bool s_perTilePaintingEnabled = false; +static bool s_partialSwapEnabled = false; +static bool s_acceleratedAnimationEnabled = false; +} // namespace + +namespace WebCore { + +bool CCSettings::perTilePaintingEnabled() { return s_perTilePaintingEnabled; } +void CCSettings::setPerTilePaintingEnabled(bool enabled) { s_perTilePaintingEnabled = enabled; } + +bool CCSettings::partialSwapEnabled() { return s_partialSwapEnabled; } +void CCSettings::setPartialSwapEnabled(bool enabled) { s_partialSwapEnabled = enabled; } + +bool CCSettings::acceleratedAnimationEnabled() { return s_acceleratedAnimationEnabled; } +void CCSettings::setAcceleratedAnimationEnabled(bool enabled) { s_acceleratedAnimationEnabled = enabled; } + +void CCSettings::reset() +{ + s_perTilePaintingEnabled = false; + s_partialSwapEnabled = false; + s_acceleratedAnimationEnabled = false; +} + +} // namespace WebCore diff --git a/cc/CCSettings.h b/cc/CCSettings.h new file mode 100644 index 0000000..5bfbb86 --- /dev/null +++ b/cc/CCSettings.h @@ -0,0 +1,31 @@ +// Copyright 2012 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. + +#ifndef CCSettings_h +#define CCSettings_h + +#include "IntSize.h" + +namespace WebCore { + +class CCSettings { +public: + static bool perTilePaintingEnabled(); + static bool partialSwapEnabled(); + static bool acceleratedAnimationEnabled(); + + // These setters should only be used on the main thread before the layer + // renderer is initialized. + static void setPerTilePaintingEnabled(bool); + static void setPartialSwapEnabled(bool); + static void setAcceleratedAnimationEnabled(bool); + + // These settings are meant to be set only once, and only read thereafter. + // This function is only for resetting settings in tests. + static void reset(); +}; + +} // namespace WebCore + +#endif // CCSettings_h diff --git a/cc/CCSharedQuadState.cpp b/cc/CCSharedQuadState.cpp new file mode 100644 index 0000000..05a6622 --- /dev/null +++ b/cc/CCSharedQuadState.cpp @@ -0,0 +1,28 @@ +// Copyright 2012 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 "CCSharedQuadState.h" + +#include "FloatQuad.h" + +namespace WebCore { + +PassOwnPtr<CCSharedQuadState> CCSharedQuadState::create(const WebKit::WebTransformationMatrix& quadTransform, const IntRect& visibleContentRect, const IntRect& clippedRectInTarget, float opacity, bool opaque) +{ + return adoptPtr(new CCSharedQuadState(quadTransform, visibleContentRect, clippedRectInTarget, opacity, opaque)); +} + +CCSharedQuadState::CCSharedQuadState(const WebKit::WebTransformationMatrix& quadTransform, const IntRect& visibleContentRect, const IntRect& clippedRectInTarget, float opacity, bool opaque) + : id(-1) + , quadTransform(quadTransform) + , visibleContentRect(visibleContentRect) + , clippedRectInTarget(clippedRectInTarget) + , opacity(opacity) + , opaque(opaque) +{ +} + +} diff --git a/cc/CCSharedQuadState.h b/cc/CCSharedQuadState.h new file mode 100644 index 0000000..9e591b7 --- /dev/null +++ b/cc/CCSharedQuadState.h @@ -0,0 +1,32 @@ +// Copyright 2012 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. + +#ifndef CCSharedQuadState_h +#define CCSharedQuadState_h + +#include "IntRect.h" +#include <public/WebTransformationMatrix.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +struct CCSharedQuadState { + int id; + + // Transforms from quad's original content space to its target content space. + WebKit::WebTransformationMatrix quadTransform; + // This rect lives in the content space for the quad's originating layer. + IntRect visibleContentRect; + IntRect clippedRectInTarget; + float opacity; + bool opaque; + + static PassOwnPtr<CCSharedQuadState> create(const WebKit::WebTransformationMatrix& quadTransform, const IntRect& visibleContentRect, const IntRect& clippedRectInTarget, float opacity, bool opaque); + CCSharedQuadState(const WebKit::WebTransformationMatrix& quadTransform, const IntRect& visibleContentRect, const IntRect& clippedRectInTarget, float opacity, bool opaque); + bool isLayerAxisAlignedIntRect() const; +}; + +} + +#endif diff --git a/cc/CCSingleThreadProxy.cpp b/cc/CCSingleThreadProxy.cpp new file mode 100644 index 0000000..1ecee49 --- /dev/null +++ b/cc/CCSingleThreadProxy.cpp @@ -0,0 +1,340 @@ +// 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 "config.h" + +#include "CCSingleThreadProxy.h" + +#include "CCDrawQuad.h" +#include "CCGraphicsContext.h" +#include "CCLayerTreeHost.h" +#include "CCTextureUpdateController.h" +#include "CCTimer.h" +#include "TraceEvent.h" +#include <wtf/CurrentTime.h> + +using namespace WTF; + +namespace WebCore { + +PassOwnPtr<CCProxy> CCSingleThreadProxy::create(CCLayerTreeHost* layerTreeHost) +{ + return adoptPtr(new CCSingleThreadProxy(layerTreeHost)); +} + +CCSingleThreadProxy::CCSingleThreadProxy(CCLayerTreeHost* layerTreeHost) + : m_layerTreeHost(layerTreeHost) + , m_contextLost(false) + , m_compositorIdentifier(-1) + , m_rendererInitialized(false) + , m_nextFrameIsNewlyCommittedFrame(false) +{ + TRACE_EVENT0("cc", "CCSingleThreadProxy::CCSingleThreadProxy"); + ASSERT(CCProxy::isMainThread()); +} + +void CCSingleThreadProxy::start() +{ + DebugScopedSetImplThread impl; + m_layerTreeHostImpl = m_layerTreeHost->createLayerTreeHostImpl(this); +} + +CCSingleThreadProxy::~CCSingleThreadProxy() +{ + TRACE_EVENT0("cc", "CCSingleThreadProxy::~CCSingleThreadProxy"); + ASSERT(CCProxy::isMainThread()); + ASSERT(!m_layerTreeHostImpl && !m_layerTreeHost); // make sure stop() got called. +} + +bool CCSingleThreadProxy::compositeAndReadback(void *pixels, const IntRect& rect) +{ + TRACE_EVENT0("cc", "CCSingleThreadProxy::compositeAndReadback"); + ASSERT(CCProxy::isMainThread()); + + if (!commitAndComposite()) + return false; + + m_layerTreeHostImpl->readback(pixels, rect); + + if (m_layerTreeHostImpl->isContextLost()) + return false; + + m_layerTreeHostImpl->swapBuffers(); + didSwapFrame(); + + return true; +} + +void CCSingleThreadProxy::startPageScaleAnimation(const IntSize& targetPosition, bool useAnchor, float scale, double duration) +{ + m_layerTreeHostImpl->startPageScaleAnimation(targetPosition, useAnchor, scale, monotonicallyIncreasingTime(), duration); +} + +void CCSingleThreadProxy::finishAllRendering() +{ + ASSERT(CCProxy::isMainThread()); + { + DebugScopedSetImplThread impl; + m_layerTreeHostImpl->finishAllRendering(); + } +} + +bool CCSingleThreadProxy::isStarted() const +{ + ASSERT(CCProxy::isMainThread()); + return m_layerTreeHostImpl; +} + +bool CCSingleThreadProxy::initializeContext() +{ + ASSERT(CCProxy::isMainThread()); + OwnPtr<CCGraphicsContext> context = m_layerTreeHost->createContext(); + if (!context) + return false; + m_contextBeforeInitialization = context.release(); + return true; +} + +void CCSingleThreadProxy::setSurfaceReady() +{ + // Scheduling is controlled by the embedder in the single thread case, so nothing to do. +} + +void CCSingleThreadProxy::setVisible(bool visible) +{ + DebugScopedSetImplThread impl; + m_layerTreeHostImpl->setVisible(visible); +} + +bool CCSingleThreadProxy::initializeRenderer() +{ + ASSERT(CCProxy::isMainThread()); + ASSERT(m_contextBeforeInitialization); + { + DebugScopedSetImplThread impl; + bool ok = m_layerTreeHostImpl->initializeRenderer(m_contextBeforeInitialization.release(), UnthrottledUploader); + if (ok) { + m_rendererInitialized = true; + m_RendererCapabilitiesForMainThread = m_layerTreeHostImpl->rendererCapabilities(); + } + + return ok; + } +} + +bool CCSingleThreadProxy::recreateContext() +{ + TRACE_EVENT0("cc", "CCSingleThreadProxy::recreateContext"); + ASSERT(CCProxy::isMainThread()); + ASSERT(m_contextLost); + + OwnPtr<CCGraphicsContext> context = m_layerTreeHost->createContext(); + if (!context) + return false; + + bool initialized; + { + DebugScopedSetImplThread impl; + if (!m_layerTreeHostImpl->contentsTexturesPurged()) + m_layerTreeHost->deleteContentsTexturesOnImplThread(m_layerTreeHostImpl->resourceProvider()); + initialized = m_layerTreeHostImpl->initializeRenderer(context.release(), UnthrottledUploader); + if (initialized) { + m_RendererCapabilitiesForMainThread = m_layerTreeHostImpl->rendererCapabilities(); + } + } + + if (initialized) + m_contextLost = false; + + return initialized; +} + +void CCSingleThreadProxy::implSideRenderingStats(CCRenderingStats& stats) +{ + m_layerTreeHostImpl->renderingStats(stats); +} + +const RendererCapabilities& CCSingleThreadProxy::rendererCapabilities() const +{ + ASSERT(m_rendererInitialized); + // Note: this gets called during the commit by the "impl" thread + return m_RendererCapabilitiesForMainThread; +} + +void CCSingleThreadProxy::loseContext() +{ + ASSERT(CCProxy::isMainThread()); + m_layerTreeHost->didLoseContext(); + m_contextLost = true; +} + +void CCSingleThreadProxy::setNeedsAnimate() +{ + // CCThread-only feature + ASSERT_NOT_REACHED(); +} + +void CCSingleThreadProxy::doCommit(CCTextureUpdateQueue& queue) +{ + ASSERT(CCProxy::isMainThread()); + // Commit immediately + { + DebugScopedSetMainThreadBlocked mainThreadBlocked; + DebugScopedSetImplThread impl; + + m_layerTreeHostImpl->beginCommit(); + + m_layerTreeHost->beginCommitOnImplThread(m_layerTreeHostImpl.get()); + + // CCTextureUpdateController::updateTextures is non-blocking and will + // return without updating any textures if the uploader is busy. This + // shouldn't be a problem here as the throttled uploader isn't used in + // single thread mode. For correctness, loop until no more updates are + // pending. + while (queue.hasMoreUpdates()) + CCTextureUpdateController::updateTextures(m_layerTreeHostImpl->resourceProvider(), m_layerTreeHostImpl->renderer()->textureCopier(), m_layerTreeHostImpl->renderer()->textureUploader(), &queue, maxPartialTextureUpdates()); + + m_layerTreeHost->finishCommitOnImplThread(m_layerTreeHostImpl.get()); + + m_layerTreeHostImpl->commitComplete(); + +#if !ASSERT_DISABLED + // In the single-threaded case, the scroll deltas should never be + // touched on the impl layer tree. + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_layerTreeHostImpl->processScrollDeltas(); + ASSERT(!scrollInfo->scrolls.size()); +#endif + } + m_layerTreeHost->commitComplete(); + m_nextFrameIsNewlyCommittedFrame = true; +} + +void CCSingleThreadProxy::setNeedsCommit() +{ + ASSERT(CCProxy::isMainThread()); + m_layerTreeHost->scheduleComposite(); +} + +void CCSingleThreadProxy::setNeedsRedraw() +{ + // FIXME: Once we move render_widget scheduling into this class, we can + // treat redraw requests more efficiently than commitAndRedraw requests. + m_layerTreeHostImpl->setFullRootLayerDamage(); + setNeedsCommit(); +} + +bool CCSingleThreadProxy::commitRequested() const +{ + return false; +} + +void CCSingleThreadProxy::didAddAnimation() +{ +} + +void CCSingleThreadProxy::stop() +{ + TRACE_EVENT0("cc", "CCSingleThreadProxy::stop"); + ASSERT(CCProxy::isMainThread()); + { + DebugScopedSetMainThreadBlocked mainThreadBlocked; + DebugScopedSetImplThread impl; + + if (!m_layerTreeHostImpl->contentsTexturesPurged()) + m_layerTreeHost->deleteContentsTexturesOnImplThread(m_layerTreeHostImpl->resourceProvider()); + m_layerTreeHostImpl.clear(); + } + m_layerTreeHost = 0; +} + +void CCSingleThreadProxy::postAnimationEventsToMainThreadOnImplThread(PassOwnPtr<CCAnimationEventsVector> events, double wallClockTime) +{ + ASSERT(CCProxy::isImplThread()); + DebugScopedSetMainThread main; + m_layerTreeHost->setAnimationEvents(events, wallClockTime); +} + +// Called by the legacy scheduling path (e.g. where render_widget does the scheduling) +void CCSingleThreadProxy::compositeImmediately() +{ + if (commitAndComposite()) { + m_layerTreeHostImpl->swapBuffers(); + didSwapFrame(); + } +} + +void CCSingleThreadProxy::forceSerializeOnSwapBuffers() +{ + { + DebugScopedSetImplThread impl; + if (m_rendererInitialized) + m_layerTreeHostImpl->renderer()->doNoOp(); + } +} + +bool CCSingleThreadProxy::commitAndComposite() +{ + ASSERT(CCProxy::isMainThread()); + + + if (!m_layerTreeHost->initializeRendererIfNeeded()) + return false; + + if (m_layerTreeHostImpl->contentsTexturesPurged()) + m_layerTreeHost->evictAllContentTextures(); + + CCTextureUpdateQueue queue; + m_layerTreeHost->updateLayers(queue, m_layerTreeHostImpl->memoryAllocationLimitBytes()); + m_layerTreeHostImpl->resetContentsTexturesPurged(); + + m_layerTreeHost->willCommit(); + doCommit(queue); + bool result = doComposite(); + m_layerTreeHost->didBeginFrame(); + return result; +} + +bool CCSingleThreadProxy::doComposite() +{ + ASSERT(!m_contextLost); + { + DebugScopedSetImplThread impl; + + if (!m_layerTreeHostImpl->visible()) + return false; + + double monotonicTime = monotonicallyIncreasingTime(); + double wallClockTime = currentTime(); + m_layerTreeHostImpl->animate(monotonicTime, wallClockTime); + + // We guard prepareToDraw() with canDraw() because it always returns a valid frame, so can only + // be used when such a frame is possible. Since drawLayers() depends on the result of + // prepareToDraw(), it is guarded on canDraw() as well. + if (!m_layerTreeHostImpl->canDraw()) + return false; + + CCLayerTreeHostImpl::FrameData frame; + m_layerTreeHostImpl->prepareToDraw(frame); + m_layerTreeHostImpl->drawLayers(frame); + m_layerTreeHostImpl->didDrawAllLayers(frame); + } + + if (m_layerTreeHostImpl->isContextLost()) { + m_contextLost = true; + m_layerTreeHost->didLoseContext(); + return false; + } + + return true; +} + +void CCSingleThreadProxy::didSwapFrame() +{ + if (m_nextFrameIsNewlyCommittedFrame) { + m_nextFrameIsNewlyCommittedFrame = false; + m_layerTreeHost->didCommitAndDrawFrame(); + } +} + +} diff --git a/cc/CCSingleThreadProxy.h b/cc/CCSingleThreadProxy.h new file mode 100644 index 0000000..37a970c --- /dev/null +++ b/cc/CCSingleThreadProxy.h @@ -0,0 +1,122 @@ +// 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. + +#ifndef CCSingleThreadProxy_h +#define CCSingleThreadProxy_h + +#include "CCAnimationEvents.h" +#include "CCLayerTreeHostImpl.h" +#include "CCProxy.h" +#include <limits> +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class CCLayerTreeHost; + +class CCSingleThreadProxy : public CCProxy, CCLayerTreeHostImplClient { +public: + static PassOwnPtr<CCProxy> create(CCLayerTreeHost*); + virtual ~CCSingleThreadProxy(); + + // CCProxy implementation + virtual bool compositeAndReadback(void *pixels, const IntRect&) OVERRIDE; + virtual void startPageScaleAnimation(const IntSize& targetPosition, bool useAnchor, float scale, double duration) OVERRIDE; + virtual void finishAllRendering() OVERRIDE; + virtual bool isStarted() const OVERRIDE; + virtual bool initializeContext() OVERRIDE; + virtual void setSurfaceReady() OVERRIDE; + virtual void setVisible(bool) OVERRIDE; + virtual bool initializeRenderer() OVERRIDE; + virtual bool recreateContext() OVERRIDE; + virtual int compositorIdentifier() const OVERRIDE { return m_compositorIdentifier; } + virtual void implSideRenderingStats(CCRenderingStats&) OVERRIDE; + virtual const RendererCapabilities& rendererCapabilities() const OVERRIDE; + virtual void loseContext() OVERRIDE; + virtual void setNeedsAnimate() OVERRIDE; + virtual void setNeedsCommit() OVERRIDE; + virtual void setNeedsRedraw() OVERRIDE; + virtual bool commitRequested() const OVERRIDE; + virtual void didAddAnimation() OVERRIDE; + virtual void start() OVERRIDE; + virtual void stop() OVERRIDE; + virtual size_t maxPartialTextureUpdates() const OVERRIDE { return std::numeric_limits<size_t>::max(); } + virtual void acquireLayerTextures() OVERRIDE { } + virtual void forceSerializeOnSwapBuffers() OVERRIDE; + + // CCLayerTreeHostImplClient implementation + virtual void didLoseContextOnImplThread() OVERRIDE { } + virtual void onSwapBuffersCompleteOnImplThread() OVERRIDE { ASSERT_NOT_REACHED(); } + virtual void onVSyncParametersChanged(double monotonicTimebase, double intervalInSeconds) OVERRIDE { } + virtual void setNeedsRedrawOnImplThread() OVERRIDE { m_layerTreeHost->scheduleComposite(); } + virtual void setNeedsCommitOnImplThread() OVERRIDE { m_layerTreeHost->scheduleComposite(); } + virtual void postAnimationEventsToMainThreadOnImplThread(PassOwnPtr<CCAnimationEventsVector>, double wallClockTime) OVERRIDE; + + // Called by the legacy path where RenderWidget does the scheduling. + void compositeImmediately(); + +private: + explicit CCSingleThreadProxy(CCLayerTreeHost*); + + bool commitAndComposite(); + void doCommit(CCTextureUpdateQueue&); + bool doComposite(); + void didSwapFrame(); + + // Accessed on main thread only. + CCLayerTreeHost* m_layerTreeHost; + bool m_contextLost; + int m_compositorIdentifier; + + // Holds on to the context between initializeContext() and initializeRenderer() calls. Shouldn't + // be used for anything else. + OwnPtr<CCGraphicsContext> m_contextBeforeInitialization; + + // Used on the CCThread, but checked on main thread during initialization/shutdown. + OwnPtr<CCLayerTreeHostImpl> m_layerTreeHostImpl; + bool m_rendererInitialized; + RendererCapabilities m_RendererCapabilitiesForMainThread; + + bool m_nextFrameIsNewlyCommittedFrame; +}; + +// For use in the single-threaded case. In debug builds, it pretends that the +// code is running on the thread to satisfy assertion checks. +class DebugScopedSetImplThread { +public: + DebugScopedSetImplThread() + { +#if !ASSERT_DISABLED + CCProxy::setCurrentThreadIsImplThread(true); +#endif + } + ~DebugScopedSetImplThread() + { +#if !ASSERT_DISABLED + CCProxy::setCurrentThreadIsImplThread(false); +#endif + } +}; + +// For use in the single-threaded case. In debug builds, it pretends that the +// code is running on the main thread to satisfy assertion checks. +class DebugScopedSetMainThread { +public: + DebugScopedSetMainThread() + { +#if !ASSERT_DISABLED + CCProxy::setCurrentThreadIsImplThread(false); +#endif + } + ~DebugScopedSetMainThread() + { +#if !ASSERT_DISABLED + CCProxy::setCurrentThreadIsImplThread(true); +#endif + } +}; + +} // namespace WebCore + +#endif diff --git a/cc/CCSolidColorDrawQuad.cpp b/cc/CCSolidColorDrawQuad.cpp new file mode 100644 index 0000000..26f0d8a --- /dev/null +++ b/cc/CCSolidColorDrawQuad.cpp @@ -0,0 +1,32 @@ +// Copyright 2012 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 "CCSolidColorDrawQuad.h" + +namespace WebCore { + +PassOwnPtr<CCSolidColorDrawQuad> CCSolidColorDrawQuad::create(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, SkColor color) +{ + return adoptPtr(new CCSolidColorDrawQuad(sharedQuadState, quadRect, color)); +} + +CCSolidColorDrawQuad::CCSolidColorDrawQuad(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, SkColor color) + : CCDrawQuad(sharedQuadState, CCDrawQuad::SolidColor, quadRect) + , m_color(color) +{ + if (SkColorGetA(m_color) < 255) + m_quadOpaque = false; + else + m_opaqueRect = quadRect; +} + +const CCSolidColorDrawQuad* CCSolidColorDrawQuad::materialCast(const CCDrawQuad* quad) +{ + ASSERT(quad->material() == CCDrawQuad::SolidColor); + return static_cast<const CCSolidColorDrawQuad*>(quad); +} + +} diff --git a/cc/CCSolidColorDrawQuad.h b/cc/CCSolidColorDrawQuad.h new file mode 100644 index 0000000..a14c81a --- /dev/null +++ b/cc/CCSolidColorDrawQuad.h @@ -0,0 +1,33 @@ +// Copyright 2012 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. + +#ifndef CCSolidColorDrawQuad_h +#define CCSolidColorDrawQuad_h + +#include "CCDrawQuad.h" +#include "SkColor.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +#pragma pack(push, 4) + +class CCSolidColorDrawQuad : public CCDrawQuad { +public: + static PassOwnPtr<CCSolidColorDrawQuad> create(const CCSharedQuadState*, const IntRect&, SkColor); + + SkColor color() const { return m_color; }; + + static const CCSolidColorDrawQuad* materialCast(const CCDrawQuad*); +private: + CCSolidColorDrawQuad(const CCSharedQuadState*, const IntRect&, SkColor); + + SkColor m_color; +}; + +#pragma pack(pop) + +} + +#endif diff --git a/cc/CCSolidColorLayerImpl.cpp b/cc/CCSolidColorLayerImpl.cpp new file mode 100644 index 0000000..4609d56 --- /dev/null +++ b/cc/CCSolidColorLayerImpl.cpp @@ -0,0 +1,49 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCSolidColorLayerImpl.h" + +#include "CCQuadSink.h" +#include "CCSolidColorDrawQuad.h" +#include <wtf/MathExtras.h> +#include <wtf/text/WTFString.h> + +using namespace std; +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +CCSolidColorLayerImpl::CCSolidColorLayerImpl(int id) + : CCLayerImpl(id) + , m_tileSize(256) +{ +} + +CCSolidColorLayerImpl::~CCSolidColorLayerImpl() +{ +} + +void CCSolidColorLayerImpl::appendQuads(CCQuadSink& quadSink, bool&) +{ + CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState()); + appendDebugBorderQuad(quadSink, sharedQuadState); + + // We create a series of smaller quads instead of just one large one so that the + // culler can reduce the total pixels drawn. + int width = contentBounds().width(); + int height = contentBounds().height(); + for (int x = 0; x < width; x += m_tileSize) { + for (int y = 0; y < height; y += m_tileSize) { + IntRect solidTileRect(x, y, min(width - x, m_tileSize), min(height - y, m_tileSize)); + quadSink.append(CCSolidColorDrawQuad::create(sharedQuadState, solidTileRect, backgroundColor())); + } + } +} + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCSolidColorLayerImpl.h b/cc/CCSolidColorLayerImpl.h new file mode 100644 index 0000000..ea1000a --- /dev/null +++ b/cc/CCSolidColorLayerImpl.h @@ -0,0 +1,34 @@ +// Copyright 2012 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. + +#ifndef CCSolidColorLayerImpl_h +#define CCSolidColorLayerImpl_h + +#include "CCLayerImpl.h" +#include <public/WebTransformationMatrix.h> + +namespace WebCore { + +class CCSolidColorLayerImpl : public CCLayerImpl { +public: + static PassOwnPtr<CCSolidColorLayerImpl> create(int id) + { + return adoptPtr(new CCSolidColorLayerImpl(id)); + } + virtual ~CCSolidColorLayerImpl(); + + virtual void appendQuads(CCQuadSink&, bool& hadMissingTiles) OVERRIDE; + +protected: + explicit CCSolidColorLayerImpl(int id); + +private: + virtual const char* layerTypeAsString() const OVERRIDE { return "SolidColorLayer"; } + + const int m_tileSize; +}; + +} + +#endif // CCSolidColorLayerImpl_h diff --git a/cc/CCSolidColorLayerImplTest.cpp b/cc/CCSolidColorLayerImplTest.cpp new file mode 100644 index 0000000..18db936 --- /dev/null +++ b/cc/CCSolidColorLayerImplTest.cpp @@ -0,0 +1,92 @@ +// Copyright 2012 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 "CCSolidColorLayerImpl.h" + +#include "CCLayerTestCommon.h" +#include "CCSingleThreadProxy.h" +#include "CCSolidColorDrawQuad.h" +#include "MockCCQuadCuller.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using namespace WebCore; +using namespace CCLayerTestCommon; + +namespace { + +TEST(CCSolidColorLayerImplTest, verifyTilingCompleteAndNoOverlap) +{ + DebugScopedSetImplThread scopedImplThread; + + MockCCQuadCuller quadCuller; + IntSize layerSize = IntSize(800, 600); + IntRect visibleContentRect = IntRect(IntPoint(), layerSize); + + OwnPtr<CCSolidColorLayerImpl> layer = CCSolidColorLayerImpl::create(1); + layer->setVisibleContentRect(visibleContentRect); + layer->setBounds(layerSize); + layer->setContentBounds(layerSize); + layer->createRenderSurface(); + layer->setRenderTarget(layer.get()); + + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); + + verifyQuadsExactlyCoverRect(quadCuller.quadList(), visibleContentRect); +} + +TEST(CCSolidColorLayerImplTest, verifyCorrectBackgroundColorInQuad) +{ + DebugScopedSetImplThread scopedImplThread; + + SkColor testColor = 0xFFA55AFF; + + MockCCQuadCuller quadCuller; + IntSize layerSize = IntSize(100, 100); + IntRect visibleContentRect = IntRect(IntPoint(), layerSize); + + OwnPtr<CCSolidColorLayerImpl> layer = CCSolidColorLayerImpl::create(1); + layer->setVisibleContentRect(visibleContentRect); + layer->setBounds(layerSize); + layer->setContentBounds(layerSize); + layer->setBackgroundColor(testColor); + layer->createRenderSurface(); + layer->setRenderTarget(layer.get()); + + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); + + ASSERT_EQ(quadCuller.quadList().size(), 1U); + EXPECT_EQ(CCSolidColorDrawQuad::materialCast(quadCuller.quadList()[0].get())->color(), testColor); +} + +TEST(CCSolidColorLayerImplTest, verifyCorrectOpacityInQuad) +{ + DebugScopedSetImplThread scopedImplThread; + + const float opacity = 0.5f; + + MockCCQuadCuller quadCuller; + IntSize layerSize = IntSize(100, 100); + IntRect visibleContentRect = IntRect(IntPoint(), layerSize); + + OwnPtr<CCSolidColorLayerImpl> layer = CCSolidColorLayerImpl::create(1); + layer->setVisibleContentRect(visibleContentRect); + layer->setBounds(layerSize); + layer->setContentBounds(layerSize); + layer->setDrawOpacity(opacity); + layer->createRenderSurface(); + layer->setRenderTarget(layer.get()); + + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); + + ASSERT_EQ(quadCuller.quadList().size(), 1U); + EXPECT_EQ(opacity, CCSolidColorDrawQuad::materialCast(quadCuller.quadList()[0].get())->opacity()); +} + +} // namespace diff --git a/cc/CCStreamVideoDrawQuad.cpp b/cc/CCStreamVideoDrawQuad.cpp new file mode 100644 index 0000000..c766f98 --- /dev/null +++ b/cc/CCStreamVideoDrawQuad.cpp @@ -0,0 +1,29 @@ +// Copyright 2012 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 "CCStreamVideoDrawQuad.h" + +namespace WebCore { + +PassOwnPtr<CCStreamVideoDrawQuad> CCStreamVideoDrawQuad::create(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, unsigned textureId, const WebKit::WebTransformationMatrix& matrix) +{ + return adoptPtr(new CCStreamVideoDrawQuad(sharedQuadState, quadRect, textureId, matrix)); +} + +CCStreamVideoDrawQuad::CCStreamVideoDrawQuad(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, unsigned textureId, const WebKit::WebTransformationMatrix& matrix) + : CCDrawQuad(sharedQuadState, CCDrawQuad::StreamVideoContent, quadRect) + , m_textureId(textureId) + , m_matrix(matrix) +{ +} + +const CCStreamVideoDrawQuad* CCStreamVideoDrawQuad::materialCast(const CCDrawQuad* quad) +{ + ASSERT(quad->material() == CCDrawQuad::StreamVideoContent); + return static_cast<const CCStreamVideoDrawQuad*>(quad); +} + +} diff --git a/cc/CCStreamVideoDrawQuad.h b/cc/CCStreamVideoDrawQuad.h new file mode 100644 index 0000000..2ad0ed8 --- /dev/null +++ b/cc/CCStreamVideoDrawQuad.h @@ -0,0 +1,36 @@ +// Copyright 2012 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. + +#ifndef CCStreamVideoDrawQuad_h +#define CCStreamVideoDrawQuad_h + +#include "CCDrawQuad.h" +#include <public/WebTransformationMatrix.h> + +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +#pragma pack(push, 4) + +class CCStreamVideoDrawQuad : public CCDrawQuad { +public: + static PassOwnPtr<CCStreamVideoDrawQuad> create(const CCSharedQuadState*, const IntRect&, unsigned textureId, const WebKit::WebTransformationMatrix&); + + unsigned textureId() const { return m_textureId; } + const WebKit::WebTransformationMatrix& matrix() const { return m_matrix; } + + static const CCStreamVideoDrawQuad* materialCast(const CCDrawQuad*); +private: + CCStreamVideoDrawQuad(const CCSharedQuadState*, const IntRect&, unsigned textureId, const WebKit::WebTransformationMatrix&); + + unsigned m_textureId; + WebKit::WebTransformationMatrix m_matrix; +}; + +#pragma pack(pop) + +} + +#endif diff --git a/cc/CCTexture.cpp b/cc/CCTexture.cpp new file mode 100644 index 0000000..bcd1ea3 --- /dev/null +++ b/cc/CCTexture.cpp @@ -0,0 +1,36 @@ +// Copyright 2012 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 "CCTexture.h" + +namespace WebCore { + +void CCTexture::setDimensions(const IntSize& size, GC3Denum format) +{ + m_size = size; + m_format = format; +} + +size_t CCTexture::bytes() const +{ + if (m_size.isEmpty()) + return 0u; + + return memorySizeBytes(m_size, m_format); +} + +size_t CCTexture::memorySizeBytes(const IntSize& size, GC3Denum format) +{ + unsigned int componentsPerPixel; + unsigned int bytesPerComponent; + if (!GraphicsContext3D::computeFormatAndTypeParameters(format, GraphicsContext3D::UNSIGNED_BYTE, &componentsPerPixel, &bytesPerComponent)) { + ASSERT_NOT_REACHED(); + return 0u; + } + return componentsPerPixel * bytesPerComponent * size.width() * size.height(); +} + +} diff --git a/cc/CCTexture.h b/cc/CCTexture.h new file mode 100644 index 0000000..02699d27 --- /dev/null +++ b/cc/CCTexture.h @@ -0,0 +1,42 @@ +// Copyright 2012 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. + +#ifndef CCTexture_h +#define CCTexture_h + +#include "CCResourceProvider.h" +#include "CCTexture.h" +#include "GraphicsContext3D.h" +#include "IntSize.h" + +namespace WebCore { + +class CCTexture { +public: + CCTexture() : m_id(0) { } + CCTexture(unsigned id, IntSize size, GC3Denum format) + : m_id(id) + , m_size(size) + , m_format(format) { } + + CCResourceProvider::ResourceId id() const { return m_id; } + const IntSize& size() const { return m_size; } + GC3Denum format() const { return m_format; } + + void setId(CCResourceProvider::ResourceId id) { m_id = id; } + void setDimensions(const IntSize&, GC3Denum format); + + size_t bytes() const; + + static size_t memorySizeBytes(const IntSize&, GC3Denum format); + +private: + CCResourceProvider::ResourceId m_id; + IntSize m_size; + GC3Denum m_format; +}; + +} + +#endif diff --git a/cc/CCTextureDrawQuad.cpp b/cc/CCTextureDrawQuad.cpp new file mode 100644 index 0000000..4451ad2 --- /dev/null +++ b/cc/CCTextureDrawQuad.cpp @@ -0,0 +1,36 @@ +// Copyright 2012 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 "CCTextureDrawQuad.h" + +namespace WebCore { + +PassOwnPtr<CCTextureDrawQuad> CCTextureDrawQuad::create(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, unsigned resourceId, bool premultipliedAlpha, const FloatRect& uvRect, bool flipped) +{ + return adoptPtr(new CCTextureDrawQuad(sharedQuadState, quadRect, resourceId, premultipliedAlpha, uvRect, flipped)); +} + +CCTextureDrawQuad::CCTextureDrawQuad(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, unsigned resourceId, bool premultipliedAlpha, const FloatRect& uvRect, bool flipped) + : CCDrawQuad(sharedQuadState, CCDrawQuad::TextureContent, quadRect) + , m_resourceId(resourceId) + , m_premultipliedAlpha(premultipliedAlpha) + , m_uvRect(uvRect) + , m_flipped(flipped) +{ +} + +void CCTextureDrawQuad::setNeedsBlending() +{ + m_needsBlending = true; +} + +const CCTextureDrawQuad* CCTextureDrawQuad::materialCast(const CCDrawQuad* quad) +{ + ASSERT(quad->material() == CCDrawQuad::TextureContent); + return static_cast<const CCTextureDrawQuad*>(quad); +} + +} diff --git a/cc/CCTextureDrawQuad.h b/cc/CCTextureDrawQuad.h new file mode 100644 index 0000000..fd0a6d0 --- /dev/null +++ b/cc/CCTextureDrawQuad.h @@ -0,0 +1,41 @@ +// Copyright 2012 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. + +#ifndef CCTextureDrawQuad_h +#define CCTextureDrawQuad_h + +#include "CCDrawQuad.h" +#include "FloatRect.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +#pragma pack(push, 4) + +class CCTextureDrawQuad : public CCDrawQuad { +public: + static PassOwnPtr<CCTextureDrawQuad> create(const CCSharedQuadState*, const IntRect&, unsigned resourceId, bool premultipliedAlpha, const FloatRect& uvRect, bool flipped); + FloatRect uvRect() const { return m_uvRect; } + + unsigned resourceId() const { return m_resourceId; } + bool premultipliedAlpha() const { return m_premultipliedAlpha; } + bool flipped() const { return m_flipped; } + + void setNeedsBlending(); + + static const CCTextureDrawQuad* materialCast(const CCDrawQuad*); +private: + CCTextureDrawQuad(const CCSharedQuadState*, const IntRect&, unsigned resourceId, bool premultipliedAlpha, const FloatRect& uvRect, bool flipped); + + unsigned m_resourceId; + bool m_premultipliedAlpha; + FloatRect m_uvRect; + bool m_flipped; +}; + +#pragma pack(pop) + +} + +#endif diff --git a/cc/CCTextureLayerImpl.cpp b/cc/CCTextureLayerImpl.cpp new file mode 100644 index 0000000..943c423 --- /dev/null +++ b/cc/CCTextureLayerImpl.cpp @@ -0,0 +1,79 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCTextureLayerImpl.h" + +#include "CCQuadSink.h" +#include "CCRenderer.h" +#include "CCTextureDrawQuad.h" +#include "TextStream.h" + +namespace WebCore { + +CCTextureLayerImpl::CCTextureLayerImpl(int id) + : CCLayerImpl(id) + , m_textureId(0) + , m_externalTextureResource(0) + , m_premultipliedAlpha(true) + , m_flipped(true) + , m_uvRect(0, 0, 1, 1) +{ +} + +CCTextureLayerImpl::~CCTextureLayerImpl() +{ +} + +void CCTextureLayerImpl::willDraw(CCResourceProvider* resourceProvider) +{ + if (!m_textureId) + return; + ASSERT(!m_externalTextureResource); + m_externalTextureResource = resourceProvider->createResourceFromExternalTexture(m_textureId); +} + +void CCTextureLayerImpl::appendQuads(CCQuadSink& quadSink, bool&) +{ + if (!m_externalTextureResource) + return; + + CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState()); + appendDebugBorderQuad(quadSink, sharedQuadState); + + IntRect quadRect(IntPoint(), contentBounds()); + quadSink.append(CCTextureDrawQuad::create(sharedQuadState, quadRect, m_externalTextureResource, m_premultipliedAlpha, m_uvRect, m_flipped)); +} + +void CCTextureLayerImpl::didDraw(CCResourceProvider* resourceProvider) +{ + if (!m_externalTextureResource) + return; + // FIXME: the following assert will not be true when sending resources to a + // parent compositor. A synchronization scheme (double-buffering or + // pipelining of updates) for the client will need to exist to solve this. + ASSERT(!resourceProvider->inUseByConsumer(m_externalTextureResource)); + resourceProvider->deleteResource(m_externalTextureResource); + m_externalTextureResource = 0; +} + +void CCTextureLayerImpl::dumpLayerProperties(TextStream& ts, int indent) const +{ + writeIndent(ts, indent); + ts << "texture layer texture id: " << m_textureId << " premultiplied: " << m_premultipliedAlpha << "\n"; + CCLayerImpl::dumpLayerProperties(ts, indent); +} + +void CCTextureLayerImpl::didLoseContext() +{ + m_textureId = 0; + m_externalTextureResource = 0; +} + +} + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCTextureLayerImpl.h b/cc/CCTextureLayerImpl.h new file mode 100644 index 0000000..37cf83f --- /dev/null +++ b/cc/CCTextureLayerImpl.h @@ -0,0 +1,48 @@ +// Copyright 2012 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. + +#ifndef CCTextureLayerImpl_h +#define CCTextureLayerImpl_h + +#include "CCLayerImpl.h" + +namespace WebCore { + +class CCTextureLayerImpl : public CCLayerImpl { +public: + static PassOwnPtr<CCTextureLayerImpl> create(int id) + { + return adoptPtr(new CCTextureLayerImpl(id)); + } + virtual ~CCTextureLayerImpl(); + + virtual void willDraw(CCResourceProvider*) OVERRIDE; + virtual void appendQuads(CCQuadSink&, bool& hadMissingTiles) OVERRIDE; + virtual void didDraw(CCResourceProvider*) OVERRIDE; + + virtual void didLoseContext() OVERRIDE; + + virtual void dumpLayerProperties(TextStream&, int indent) const OVERRIDE; + + unsigned textureId() const { return m_textureId; } + void setTextureId(unsigned id) { m_textureId = id; } + void setPremultipliedAlpha(bool premultipliedAlpha) { m_premultipliedAlpha = premultipliedAlpha; } + void setFlipped(bool flipped) { m_flipped = flipped; } + void setUVRect(const FloatRect& rect) { m_uvRect = rect; } + +private: + explicit CCTextureLayerImpl(int); + + virtual const char* layerTypeAsString() const OVERRIDE { return "TextureLayer"; } + + unsigned m_textureId; + CCResourceProvider::ResourceId m_externalTextureResource; + bool m_premultipliedAlpha; + bool m_flipped; + FloatRect m_uvRect; +}; + +} + +#endif // CCTextureLayerImpl_h diff --git a/cc/CCTextureUpdateController.cpp b/cc/CCTextureUpdateController.cpp new file mode 100644 index 0000000..25d41cc --- /dev/null +++ b/cc/CCTextureUpdateController.cpp @@ -0,0 +1,168 @@ +// Copyright 2012 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 "CCTextureUpdateController.h" + +#include "GraphicsContext3D.h" +#include "TextureCopier.h" +#include "TextureUploader.h" +#include <wtf/CurrentTime.h> + +namespace { + +// Number of textures to update with each call to updateMoreTexturesIfEnoughTimeRemaining(). +static const size_t textureUpdatesPerTick = 12; + +// Measured in seconds. +static const double textureUpdateTickRate = 0.004; + +// Flush interval when performing texture uploads. +static const int textureUploadFlushPeriod = 4; + +} // anonymous namespace + +namespace WebCore { + +size_t CCTextureUpdateController::maxPartialTextureUpdates() +{ + return textureUpdatesPerTick; +} + +void CCTextureUpdateController::updateTextures(CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader, CCTextureUpdateQueue* queue, size_t count) +{ + if (queue->fullUploadSize() || queue->partialUploadSize()) { + if (uploader->isBusy()) + return; + + uploader->beginUploads(); + + size_t fullUploadCount = 0; + while (queue->fullUploadSize() && fullUploadCount < count) { + uploader->uploadTexture(resourceProvider, queue->takeFirstFullUpload()); + fullUploadCount++; + if (!(fullUploadCount % textureUploadFlushPeriod)) + resourceProvider->shallowFlushIfSupported(); + } + + // Make sure there are no dangling uploads without a flush. + if (fullUploadCount % textureUploadFlushPeriod) + resourceProvider->shallowFlushIfSupported(); + + bool moreUploads = queue->fullUploadSize(); + + ASSERT(queue->partialUploadSize() <= count); + // We need another update batch if the number of updates remaining + // in |count| is greater than the remaining partial entries. + if ((count - fullUploadCount) < queue->partialUploadSize()) + moreUploads = true; + + if (moreUploads) { + uploader->endUploads(); + return; + } + + size_t partialUploadCount = 0; + while (queue->partialUploadSize()) { + uploader->uploadTexture(resourceProvider, queue->takeFirstPartialUpload()); + partialUploadCount++; + if (!(partialUploadCount % textureUploadFlushPeriod)) + resourceProvider->shallowFlushIfSupported(); + } + + // Make sure there are no dangling partial uploads without a flush. + if (partialUploadCount % textureUploadFlushPeriod) + resourceProvider->shallowFlushIfSupported(); + + uploader->endUploads(); + } + + size_t copyCount = 0; + while (queue->copySize()) { + copier->copyTexture(queue->takeFirstCopy()); + copyCount++; + } + + // If we've performed any texture copies, we need to insert a flush here into the compositor context + // before letting the main thread proceed as it may make draw calls to the source texture of one of + // our copy operations. + if (copyCount) + copier->flush(); +} + +CCTextureUpdateController::CCTextureUpdateController(CCThread* thread, PassOwnPtr<CCTextureUpdateQueue> queue, CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader) + : m_timer(adoptPtr(new CCTimer(thread, this))) + , m_queue(queue) + , m_resourceProvider(resourceProvider) + , m_copier(copier) + , m_uploader(uploader) + , m_monotonicTimeLimit(0) + , m_firstUpdateAttempt(true) +{ +} + +CCTextureUpdateController::~CCTextureUpdateController() +{ +} + +bool CCTextureUpdateController::hasMoreUpdates() const +{ + return m_queue->hasMoreUpdates(); +} + +void CCTextureUpdateController::updateMoreTextures(double monotonicTimeLimit) +{ + m_monotonicTimeLimit = monotonicTimeLimit; + + if (!m_queue->hasMoreUpdates()) + return; + + // Call updateMoreTexturesNow() directly unless it's the first update + // attempt. This ensures that we empty the update queue in a finite + // amount of time. + if (m_firstUpdateAttempt) { + updateMoreTexturesIfEnoughTimeRemaining(); + m_firstUpdateAttempt = false; + } else + updateMoreTexturesNow(); +} + +void CCTextureUpdateController::onTimerFired() +{ + if (!m_queue->hasMoreUpdates()) + return; + + updateMoreTexturesIfEnoughTimeRemaining(); +} + +double CCTextureUpdateController::monotonicTimeNow() const +{ + return monotonicallyIncreasingTime(); +} + +double CCTextureUpdateController::updateMoreTexturesTime() const +{ + return textureUpdateTickRate; +} + +size_t CCTextureUpdateController::updateMoreTexturesSize() const +{ + return textureUpdatesPerTick; +} + +void CCTextureUpdateController::updateMoreTexturesIfEnoughTimeRemaining() +{ + bool hasTimeRemaining = monotonicTimeNow() < m_monotonicTimeLimit - updateMoreTexturesTime(); + if (hasTimeRemaining) + updateMoreTexturesNow(); +} + +void CCTextureUpdateController::updateMoreTexturesNow() +{ + m_timer->startOneShot(updateMoreTexturesTime()); + updateTextures(m_resourceProvider, m_copier, m_uploader, m_queue.get(), updateMoreTexturesSize()); +} + +} diff --git a/cc/CCTextureUpdateController.h b/cc/CCTextureUpdateController.h new file mode 100644 index 0000000..6fc8db3 --- /dev/null +++ b/cc/CCTextureUpdateController.h @@ -0,0 +1,59 @@ +// Copyright 2012 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. + +#ifndef CCTextureUpdateController_h +#define CCTextureUpdateController_h + +#include "CCTextureUpdateQueue.h" +#include "CCTimer.h" +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class TextureCopier; +class TextureUploader; + +class CCTextureUpdateController : public CCTimerClient { + WTF_MAKE_NONCOPYABLE(CCTextureUpdateController); +public: + static PassOwnPtr<CCTextureUpdateController> create(CCThread* thread, PassOwnPtr<CCTextureUpdateQueue> queue, CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader) + { + return adoptPtr(new CCTextureUpdateController(thread, queue, resourceProvider, copier, uploader)); + } + static size_t maxPartialTextureUpdates(); + static void updateTextures(CCResourceProvider*, TextureCopier*, TextureUploader*, CCTextureUpdateQueue*, size_t count); + + virtual ~CCTextureUpdateController(); + + bool hasMoreUpdates() const; + void updateMoreTextures(double monotonicTimeLimit); + + // CCTimerClient implementation. + virtual void onTimerFired() OVERRIDE; + + // Virtual for testing. + virtual double monotonicTimeNow() const; + virtual double updateMoreTexturesTime() const; + virtual size_t updateMoreTexturesSize() const; + +protected: + CCTextureUpdateController(CCThread*, PassOwnPtr<CCTextureUpdateQueue>, CCResourceProvider*, TextureCopier*, TextureUploader*); + + void updateMoreTexturesIfEnoughTimeRemaining(); + void updateMoreTexturesNow(); + + OwnPtr<CCTimer> m_timer; + OwnPtr<CCTextureUpdateQueue> m_queue; + bool m_contentsTexturesPurged; + CCResourceProvider* m_resourceProvider; + TextureCopier* m_copier; + TextureUploader* m_uploader; + double m_monotonicTimeLimit; + bool m_firstUpdateAttempt; +}; + +} + +#endif // CCTextureUpdateController_h diff --git a/cc/CCTextureUpdateControllerTest.cpp b/cc/CCTextureUpdateControllerTest.cpp new file mode 100644 index 0000000..2ed0e59 --- /dev/null +++ b/cc/CCTextureUpdateControllerTest.cpp @@ -0,0 +1,668 @@ +// Copyright 2012 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 "CCTextureUpdateController.h" + +#include "CCSchedulerTestCommon.h" +#include "CCSingleThreadProxy.h" // For DebugScopedSetImplThread +#include "CCTiledLayerTestCommon.h" +#include "FakeWebCompositorOutputSurface.h" +#include "FakeWebGraphicsContext3D.h" +#include <gtest/gtest.h> +#include <public/WebCompositor.h> +#include <public/WebThread.h> +#include <wtf/RefPtr.h> + +using namespace WebCore; +using namespace WebKit; +using namespace WebKitTests; +using testing::Test; + + +namespace { + +const int kFlushPeriodFull = 4; +const int kFlushPeriodPartial = kFlushPeriodFull; + +class CCTextureUpdateControllerTest; + +class WebGraphicsContext3DForUploadTest : public FakeWebGraphicsContext3D { +public: + WebGraphicsContext3DForUploadTest(CCTextureUpdateControllerTest *test) + : m_test(test) + , m_supportShallowFlush(true) + { } + + virtual void flush(void); + virtual void shallowFlushCHROMIUM(void); + virtual GrGLInterface* onCreateGrGLInterface() { return 0; } + + virtual WebString getString(WGC3Denum name) + { + if (m_supportShallowFlush) + return WebString("GL_CHROMIUM_shallow_flush"); + return WebString(""); + } + +private: + CCTextureUpdateControllerTest* m_test; + bool m_supportShallowFlush; +}; + + +class TextureUploaderForUploadTest : public FakeTextureUploader { +public: + TextureUploaderForUploadTest(CCTextureUpdateControllerTest *test) : m_test(test) { } + + virtual void beginUploads() OVERRIDE; + virtual void endUploads() OVERRIDE; + virtual void uploadTexture(WebCore::CCResourceProvider*, Parameters) OVERRIDE; + +private: + CCTextureUpdateControllerTest* m_test; +}; + +class TextureForUploadTest : public LayerTextureUpdater::Texture { +public: + TextureForUploadTest() : LayerTextureUpdater::Texture(adoptPtr<CCPrioritizedTexture>(0)) { } + virtual void updateRect(CCResourceProvider*, const IntRect& sourceRect, const IntSize& destOffset) { } +}; + + +class CCTextureUpdateControllerTest : public Test { +public: + CCTextureUpdateControllerTest() + : m_queue(adoptPtr(new CCTextureUpdateQueue)) + , m_uploader(this) + , m_fullUploadCountExpected(0) + , m_partialCountExpected(0) + , m_totalUploadCountExpected(0) + , m_maxUploadCountPerUpdate(0) + , m_numBeginUploads(0) + , m_numEndUploads(0) + , m_numConsecutiveFlushes(0) + , m_numDanglingUploads(0) + , m_numTotalUploads(0) + , m_numTotalFlushes(0) + , m_numPreviousUploads(0) + , m_numPreviousFlushes(0) + { } + +public: + void onFlush() + { + // Check for back-to-back flushes. + EXPECT_EQ(0, m_numConsecutiveFlushes) << "Back-to-back flushes detected."; + + // Check for premature flushes + if (m_numPreviousUploads != m_maxUploadCountPerUpdate) { + if (m_numTotalUploads < m_fullUploadCountExpected) + EXPECT_GE(m_numDanglingUploads, kFlushPeriodFull) << "Premature flush detected in full uploads."; + else if (m_numTotalUploads > m_fullUploadCountExpected && m_numTotalUploads < m_totalUploadCountExpected) + EXPECT_GE(m_numDanglingUploads, kFlushPeriodPartial) << "Premature flush detected in partial uploads."; + } + + m_numDanglingUploads = 0; + m_numConsecutiveFlushes++; + m_numTotalFlushes++; + m_numPreviousFlushes++; + } + + void onBeginUploads() + { + m_numPreviousFlushes = 0; + m_numPreviousUploads = 0; + m_numBeginUploads++; + } + + void onUpload() + { + // Check for too many consecutive uploads + if (m_numTotalUploads < m_fullUploadCountExpected) + EXPECT_LT(m_numDanglingUploads, kFlushPeriodFull) << "Too many consecutive full uploads detected."; + else + EXPECT_LT(m_numDanglingUploads, kFlushPeriodPartial) << "Too many consecutive partial uploads detected."; + + m_numConsecutiveFlushes = 0; + m_numDanglingUploads++; + m_numTotalUploads++; + m_numPreviousUploads++; + } + + void onEndUploads() + { + EXPECT_EQ(0, m_numDanglingUploads) << "Last upload wasn't followed by a flush."; + + // Note: The m_numTotalUploads != m_fullUploadCountExpected comparison + // allows for the quota not to be hit in the case where we are trasitioning + // from full uploads to partial uploads. + if (m_numTotalUploads != m_totalUploadCountExpected && m_numTotalUploads != m_fullUploadCountExpected) { + EXPECT_EQ(m_maxUploadCountPerUpdate, m_numPreviousUploads) + << "endUpload() was called when there are textures to upload, but the upload quota hasn't been filled."; + } + + m_numEndUploads++; + } + +protected: + virtual void SetUp() + { + OwnPtr<WebThread> thread; + WebCompositor::initialize(thread.get()); + + m_context = FakeWebCompositorOutputSurface::create(adoptPtr(new WebGraphicsContext3DForUploadTest(this))); + DebugScopedSetImplThread implThread; + m_resourceProvider = CCResourceProvider::create(m_context.get()); + } + + virtual void TearDown() + { + WebCompositor::shutdown(); + } + + void appendFullUploadsToUpdateQueue(int count) + { + m_fullUploadCountExpected += count; + m_totalUploadCountExpected += count; + + const IntRect rect(0, 0, 300, 150); + const TextureUploader::Parameters upload = { &m_texture, rect, IntSize() }; + for (int i = 0; i < count; i++) + m_queue->appendFullUpload(upload); + } + + void appendPartialUploadsToUpdateQueue(int count) + { + m_partialCountExpected += count; + m_totalUploadCountExpected += count; + + const IntRect rect(0, 0, 100, 100); + const TextureUploader::Parameters upload = { &m_texture, rect, IntSize() }; + for (int i = 0; i < count; i++) + m_queue->appendPartialUpload(upload); + } + + void setMaxUploadCountPerUpdate(int count) + { + m_maxUploadCountPerUpdate = count; + } + +protected: + // Classes required to interact and test the CCTextureUpdateController + OwnPtr<CCGraphicsContext> m_context; + OwnPtr<CCResourceProvider> m_resourceProvider; + OwnPtr<CCTextureUpdateQueue> m_queue; + TextureForUploadTest m_texture; + FakeTextureCopier m_copier; + TextureUploaderForUploadTest m_uploader; + + // Properties / expectations of this test + int m_fullUploadCountExpected; + int m_partialCountExpected; + int m_totalUploadCountExpected; + int m_maxUploadCountPerUpdate; + + // Dynamic properties of this test + int m_numBeginUploads; + int m_numEndUploads; + int m_numConsecutiveFlushes; + int m_numDanglingUploads; + int m_numTotalUploads; + int m_numTotalFlushes; + int m_numPreviousUploads; + int m_numPreviousFlushes; +}; + +void WebGraphicsContext3DForUploadTest::flush(void) +{ + m_test->onFlush(); +} + +void WebGraphicsContext3DForUploadTest::shallowFlushCHROMIUM(void) +{ + m_test->onFlush(); +} + +void TextureUploaderForUploadTest::beginUploads() +{ + m_test->onBeginUploads(); +} + +void TextureUploaderForUploadTest::endUploads() +{ + m_test->onEndUploads(); +} + +void TextureUploaderForUploadTest::uploadTexture(WebCore::CCResourceProvider*, Parameters) +{ + m_test->onUpload(); +} + + +// ZERO UPLOADS TESTS +TEST_F(CCTextureUpdateControllerTest, ZeroUploads) +{ + appendFullUploadsToUpdateQueue(0); + appendPartialUploadsToUpdateQueue(0); + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + EXPECT_EQ(0, m_numBeginUploads); + EXPECT_EQ(0, m_numEndUploads); + EXPECT_EQ(0, m_numPreviousFlushes); + EXPECT_EQ(0, m_numPreviousUploads); +} + + +// ONE UPLOAD TESTS +TEST_F(CCTextureUpdateControllerTest, OneFullUpload) +{ + appendFullUploadsToUpdateQueue(1); + appendPartialUploadsToUpdateQueue(0); + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ(1, m_numPreviousFlushes); + EXPECT_EQ(1, m_numPreviousUploads); +} + +TEST_F(CCTextureUpdateControllerTest, OnePartialUpload) +{ + appendFullUploadsToUpdateQueue(0); + appendPartialUploadsToUpdateQueue(1); + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ(1, m_numPreviousFlushes); + EXPECT_EQ(1, m_numPreviousUploads); +} + +TEST_F(CCTextureUpdateControllerTest, OneFullOnePartialUpload) +{ + appendFullUploadsToUpdateQueue(1); + appendPartialUploadsToUpdateQueue(1); + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + // We expect the full uploads to be followed by a flush + // before the partial uploads begin. + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ(2, m_numPreviousFlushes); + EXPECT_EQ(2, m_numPreviousUploads); +} + + +// NO REMAINDER TESTS +// This class of tests upload a number of textures that is a multiple of the flush period. +const int fullUploadFlushMultipler = 7; +const int fullNoRemainderCount = fullUploadFlushMultipler * kFlushPeriodFull; + +const int partialUploadFlushMultipler = 11; +const int partialNoRemainderCount = partialUploadFlushMultipler * kFlushPeriodPartial; + +TEST_F(CCTextureUpdateControllerTest, ManyFullUploadsNoRemainder) +{ + appendFullUploadsToUpdateQueue(fullNoRemainderCount); + appendPartialUploadsToUpdateQueue(0); + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ(fullUploadFlushMultipler, m_numPreviousFlushes); + EXPECT_EQ(fullNoRemainderCount, m_numPreviousUploads); +} + +TEST_F(CCTextureUpdateControllerTest, ManyPartialUploadsNoRemainder) +{ + appendFullUploadsToUpdateQueue(0); + appendPartialUploadsToUpdateQueue(partialNoRemainderCount); + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ(partialUploadFlushMultipler, m_numPreviousFlushes); + EXPECT_EQ(partialNoRemainderCount, m_numPreviousUploads); +} + +TEST_F(CCTextureUpdateControllerTest, ManyFullManyPartialUploadsNoRemainder) +{ + appendFullUploadsToUpdateQueue(fullNoRemainderCount); + appendPartialUploadsToUpdateQueue(partialNoRemainderCount); + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ(fullUploadFlushMultipler + partialUploadFlushMultipler, m_numPreviousFlushes); + EXPECT_EQ(fullNoRemainderCount + partialNoRemainderCount, m_numPreviousUploads); +} + + +// MIN/MAX REMAINDER TESTS +// This class of tests mix and match uploading 1 more and 1 less texture +// than a multiple of the flush period. + +const int fullMinRemainderCount = fullNoRemainderCount + 1; +const int fullMaxRemainderCount = fullNoRemainderCount - 1; +const int partialMinRemainderCount = partialNoRemainderCount + 1; +const int partialMaxRemainderCount = partialNoRemainderCount - 1; + +TEST_F(CCTextureUpdateControllerTest, ManyFullAndPartialMinRemainder) +{ + appendFullUploadsToUpdateQueue(fullMinRemainderCount); + appendPartialUploadsToUpdateQueue(partialMinRemainderCount); + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ(fullUploadFlushMultipler + partialUploadFlushMultipler + 2, m_numPreviousFlushes); + EXPECT_EQ(fullMinRemainderCount + partialMinRemainderCount, m_numPreviousUploads); +} + +TEST_F(CCTextureUpdateControllerTest, ManyFullAndPartialUploadsMaxRemainder) +{ + appendFullUploadsToUpdateQueue(fullMaxRemainderCount); + appendPartialUploadsToUpdateQueue(partialMaxRemainderCount); + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ(fullUploadFlushMultipler + partialUploadFlushMultipler, m_numPreviousFlushes); + EXPECT_EQ(fullMaxRemainderCount + partialMaxRemainderCount, m_numPreviousUploads); +} + +TEST_F(CCTextureUpdateControllerTest, ManyFullMinRemainderManyPartialMaxRemainder) +{ + appendFullUploadsToUpdateQueue(fullMinRemainderCount); + appendPartialUploadsToUpdateQueue(partialMaxRemainderCount); + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ((fullUploadFlushMultipler+1) + partialUploadFlushMultipler, m_numPreviousFlushes); + EXPECT_EQ(fullMinRemainderCount + partialMaxRemainderCount, m_numPreviousUploads); +} + +TEST_F(CCTextureUpdateControllerTest, ManyFullMaxRemainderManyPartialMinRemainder) +{ + appendFullUploadsToUpdateQueue(fullMaxRemainderCount); + appendPartialUploadsToUpdateQueue(partialMinRemainderCount); + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), m_totalUploadCountExpected); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ(fullUploadFlushMultipler + (partialUploadFlushMultipler+1), m_numPreviousFlushes); + EXPECT_EQ(fullMaxRemainderCount + partialMinRemainderCount, m_numPreviousUploads); +} + + +// MULTIPLE UPDATE TESTS +// These tests attempt to upload too many textures at once, requiring +// multiple calls to update(). + +int expectedFlushes(int uploads, int flushPeriod) +{ + return (uploads + flushPeriod - 1) / flushPeriod; +} + +TEST_F(CCTextureUpdateControllerTest, TripleUpdateFinalUpdateFullAndPartial) +{ + const int kMaxUploadsPerUpdate = 40; + const int kFullUploads = 100; + const int kPartialUploads = 20; + + int expectedPreviousFlushes = 0; + int expectedPreviousUploads = 0; + + setMaxUploadCountPerUpdate(kMaxUploadsPerUpdate); + appendFullUploadsToUpdateQueue(kFullUploads); + appendPartialUploadsToUpdateQueue(kPartialUploads); + + // First update (40 full) + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), kMaxUploadsPerUpdate); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + + expectedPreviousFlushes = expectedFlushes(kMaxUploadsPerUpdate, kFlushPeriodFull); + EXPECT_EQ(expectedPreviousFlushes, m_numPreviousFlushes); + + expectedPreviousUploads = kMaxUploadsPerUpdate; + EXPECT_EQ(expectedPreviousUploads, m_numPreviousUploads); + + // Second update (40 full) + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), kMaxUploadsPerUpdate); + + EXPECT_EQ(2, m_numBeginUploads); + EXPECT_EQ(2, m_numEndUploads); + + expectedPreviousFlushes = expectedFlushes(kMaxUploadsPerUpdate, kFlushPeriodFull); + EXPECT_EQ(expectedPreviousFlushes, m_numPreviousFlushes); + + expectedPreviousUploads = kMaxUploadsPerUpdate; + EXPECT_EQ(expectedPreviousUploads, m_numPreviousUploads); + + // Third update (20 full, 20 partial) + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), kMaxUploadsPerUpdate); + + EXPECT_EQ(3, m_numBeginUploads); + EXPECT_EQ(3, m_numEndUploads); + + expectedPreviousFlushes = expectedFlushes(kFullUploads-kMaxUploadsPerUpdate*2, kFlushPeriodFull) + + expectedFlushes(kPartialUploads, kFlushPeriodPartial); + EXPECT_EQ(expectedPreviousFlushes, m_numPreviousFlushes); + + expectedPreviousUploads = (kFullUploads-kMaxUploadsPerUpdate*2)+kPartialUploads; + EXPECT_EQ(expectedPreviousUploads, m_numPreviousUploads); + + // Final sanity checks + EXPECT_EQ(kFullUploads + kPartialUploads, m_numTotalUploads); +} + +TEST_F(CCTextureUpdateControllerTest, TripleUpdateFinalUpdateAllPartial) +{ + const int kMaxUploadsPerUpdate = 40; + const int kFullUploads = 70; + const int kPartialUploads = 30; + + int expectedPreviousFlushes = 0; + int expectedPreviousUploads = 0; + + setMaxUploadCountPerUpdate(kMaxUploadsPerUpdate); + appendFullUploadsToUpdateQueue(kFullUploads); + appendPartialUploadsToUpdateQueue(kPartialUploads); + + // First update (40 full) + DebugScopedSetImplThread implThread; + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), kMaxUploadsPerUpdate); + + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + + expectedPreviousFlushes = expectedFlushes(kMaxUploadsPerUpdate, kFlushPeriodFull); + EXPECT_EQ(expectedPreviousFlushes, m_numPreviousFlushes); + + expectedPreviousUploads = kMaxUploadsPerUpdate; + EXPECT_EQ(expectedPreviousUploads, m_numPreviousUploads); + + // Second update (30 full, optionally 10 partial) + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), kMaxUploadsPerUpdate); + + EXPECT_EQ(2, m_numBeginUploads); + EXPECT_EQ(2, m_numEndUploads); + EXPECT_LE(m_numPreviousUploads, kMaxUploadsPerUpdate); + // Be lenient on the exact number of flushes here, as the number of flushes + // will depend on whether some partial uploads were performed. + // onFlush(), onUpload(), and onEndUpload() will do basic flush checks for us anyway. + + // Third update (30 partial OR 20 partial if 10 partial uploaded in second update) + CCTextureUpdateController::updateTextures(m_resourceProvider.get(), &m_copier, &m_uploader, m_queue.get(), kMaxUploadsPerUpdate); + + EXPECT_EQ(3, m_numBeginUploads); + EXPECT_EQ(3, m_numEndUploads); + EXPECT_LE(m_numPreviousUploads, kMaxUploadsPerUpdate); + // Be lenient on the exact number of flushes here as well. + + // Final sanity checks + EXPECT_EQ(kFullUploads + kPartialUploads, m_numTotalUploads); +} + +class FakeCCTextureUpdateController : public WebCore::CCTextureUpdateController { +public: + static PassOwnPtr<FakeCCTextureUpdateController> create(WebCore::CCThread* thread, PassOwnPtr<CCTextureUpdateQueue> queue, CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader) + { + return adoptPtr(new FakeCCTextureUpdateController(thread, queue, resourceProvider, copier, uploader)); + } + + void setMonotonicTimeNow(double time) { m_monotonicTimeNow = time; } + virtual double monotonicTimeNow() const OVERRIDE { return m_monotonicTimeNow; } + void setUpdateMoreTexturesTime(double time) { m_updateMoreTexturesTime = time; } + virtual double updateMoreTexturesTime() const OVERRIDE { return m_updateMoreTexturesTime; } + void setUpdateMoreTexturesSize(size_t size) { m_updateMoreTexturesSize = size; } + virtual size_t updateMoreTexturesSize() const OVERRIDE { return m_updateMoreTexturesSize; } + +protected: + FakeCCTextureUpdateController(WebCore::CCThread* thread, PassOwnPtr<CCTextureUpdateQueue> queue, CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader) + : WebCore::CCTextureUpdateController(thread, queue, resourceProvider, copier, uploader) + , m_monotonicTimeNow(0) + , m_updateMoreTexturesTime(0) + , m_updateMoreTexturesSize(0) { } + + double m_monotonicTimeNow; + double m_updateMoreTexturesTime; + size_t m_updateMoreTexturesSize; +}; + +TEST_F(CCTextureUpdateControllerTest, UpdateMoreTextures) +{ + FakeCCThread thread; + + setMaxUploadCountPerUpdate(1); + appendFullUploadsToUpdateQueue(3); + appendPartialUploadsToUpdateQueue(0); + + DebugScopedSetImplThread implThread; + OwnPtr<FakeCCTextureUpdateController> controller(FakeCCTextureUpdateController::create(&thread, m_queue.release(), m_resourceProvider.get(), &m_copier, &m_uploader)); + + controller->setMonotonicTimeNow(0); + controller->setUpdateMoreTexturesTime(0.1); + controller->setUpdateMoreTexturesSize(1); + // Not enough time for any updates. + controller->updateMoreTextures(0.09); + EXPECT_FALSE(thread.hasPendingTask()); + EXPECT_EQ(0, m_numBeginUploads); + EXPECT_EQ(0, m_numEndUploads); + + thread.reset(); + controller->setMonotonicTimeNow(0); + controller->setUpdateMoreTexturesTime(0.1); + controller->setUpdateMoreTexturesSize(1); + // Only enough time for 1 update. + controller->updateMoreTextures(0.12); + EXPECT_TRUE(thread.hasPendingTask()); + controller->setMonotonicTimeNow(thread.pendingDelayMs() / 1000.0); + thread.runPendingTask(); + EXPECT_EQ(1, m_numBeginUploads); + EXPECT_EQ(1, m_numEndUploads); + EXPECT_EQ(1, m_numTotalUploads); + + thread.reset(); + controller->setMonotonicTimeNow(0); + controller->setUpdateMoreTexturesTime(0.1); + controller->setUpdateMoreTexturesSize(1); + // Enough time for 2 updates. + controller->updateMoreTextures(0.22); + EXPECT_TRUE(thread.hasPendingTask()); + controller->setMonotonicTimeNow(controller->monotonicTimeNow() + thread.pendingDelayMs() / 1000.0); + thread.runPendingTask(); + EXPECT_EQ(3, m_numBeginUploads); + EXPECT_EQ(3, m_numEndUploads); + EXPECT_EQ(3, m_numTotalUploads); +} + +TEST_F(CCTextureUpdateControllerTest, NoMoreUpdates) +{ + FakeCCThread thread; + + setMaxUploadCountPerUpdate(1); + appendFullUploadsToUpdateQueue(2); + appendPartialUploadsToUpdateQueue(0); + + DebugScopedSetImplThread implThread; + OwnPtr<FakeCCTextureUpdateController> controller(FakeCCTextureUpdateController::create(&thread, m_queue.release(), m_resourceProvider.get(), &m_copier, &m_uploader)); + + controller->setMonotonicTimeNow(0); + controller->setUpdateMoreTexturesTime(0.1); + controller->setUpdateMoreTexturesSize(1); + // Enough time for 3 updates but only 2 necessary. + controller->updateMoreTextures(0.31); + EXPECT_TRUE(thread.hasPendingTask()); + controller->setMonotonicTimeNow(controller->monotonicTimeNow() + thread.pendingDelayMs() / 1000.0); + thread.runPendingTask(); + EXPECT_TRUE(thread.hasPendingTask()); + controller->setMonotonicTimeNow(controller->monotonicTimeNow() + thread.pendingDelayMs() / 1000.0); + thread.runPendingTask(); + EXPECT_EQ(2, m_numBeginUploads); + EXPECT_EQ(2, m_numEndUploads); + EXPECT_EQ(2, m_numTotalUploads); + + thread.reset(); + controller->setMonotonicTimeNow(0); + controller->setUpdateMoreTexturesTime(0.1); + controller->setUpdateMoreTexturesSize(1); + // Enough time for updates but no more updates left. + controller->updateMoreTextures(0.31); + EXPECT_FALSE(thread.hasPendingTask()); + EXPECT_EQ(2, m_numBeginUploads); + EXPECT_EQ(2, m_numEndUploads); + EXPECT_EQ(2, m_numTotalUploads); +} + +TEST_F(CCTextureUpdateControllerTest, UpdatesCompleteInFiniteTime) +{ + FakeCCThread thread; + + setMaxUploadCountPerUpdate(1); + appendFullUploadsToUpdateQueue(2); + appendPartialUploadsToUpdateQueue(0); + + DebugScopedSetImplThread implThread; + OwnPtr<FakeCCTextureUpdateController> controller(FakeCCTextureUpdateController::create(&thread, m_queue.release(), m_resourceProvider.get(), &m_copier, &m_uploader)); + + controller->setMonotonicTimeNow(0); + controller->setUpdateMoreTexturesTime(0.5); + controller->setUpdateMoreTexturesSize(1); + + for (int i = 0; i < 100; i++) { + if (!controller->hasMoreUpdates()) + break; + + // Not enough time for any updates. + controller->updateMoreTextures(0.4); + + if (thread.hasPendingTask()) { + controller->setMonotonicTimeNow(controller->monotonicTimeNow() + thread.pendingDelayMs() / 1000.0); + thread.runPendingTask(); + } + } + + EXPECT_EQ(2, m_numBeginUploads); + EXPECT_EQ(2, m_numEndUploads); + EXPECT_EQ(2, m_numTotalUploads); +} + +} // namespace diff --git a/cc/CCTextureUpdateQueue.cpp b/cc/CCTextureUpdateQueue.cpp new file mode 100644 index 0000000..02f6c13 --- /dev/null +++ b/cc/CCTextureUpdateQueue.cpp @@ -0,0 +1,64 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCTextureUpdateQueue.h" + +namespace WebCore { + +CCTextureUpdateQueue::CCTextureUpdateQueue() +{ +} + +CCTextureUpdateQueue::~CCTextureUpdateQueue() +{ +} + +void CCTextureUpdateQueue::appendFullUpload(TextureUploader::Parameters upload) +{ + m_fullEntries.append(upload); +} + +void CCTextureUpdateQueue::appendPartialUpload(TextureUploader::Parameters upload) +{ + m_partialEntries.append(upload); +} + +void CCTextureUpdateQueue::appendCopy(TextureCopier::Parameters copy) +{ + m_copyEntries.append(copy); +} + +void CCTextureUpdateQueue::clearUploads() +{ + m_fullEntries.clear(); + m_partialEntries.clear(); +} + +TextureUploader::Parameters CCTextureUpdateQueue::takeFirstFullUpload() +{ + return m_fullEntries.takeFirst(); +} + +TextureUploader::Parameters CCTextureUpdateQueue::takeFirstPartialUpload() +{ + return m_partialEntries.takeFirst(); +} + +TextureCopier::Parameters CCTextureUpdateQueue::takeFirstCopy() +{ + return m_copyEntries.takeFirst(); +} + +bool CCTextureUpdateQueue::hasMoreUpdates() const +{ + return m_fullEntries.size() || m_partialEntries.size() || m_copyEntries.size(); +} + +} + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCTextureUpdateQueue.h b/cc/CCTextureUpdateQueue.h new file mode 100644 index 0000000..afe33b3 --- /dev/null +++ b/cc/CCTextureUpdateQueue.h @@ -0,0 +1,45 @@ +// Copyright 2012 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. + +#ifndef CCTextureUpdateQueue_h +#define CCTextureUpdateQueue_h + +#include "TextureCopier.h" +#include "TextureUploader.h" +#include <wtf/Deque.h> +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class CCTextureUpdateQueue { + WTF_MAKE_NONCOPYABLE(CCTextureUpdateQueue); +public: + CCTextureUpdateQueue(); + virtual ~CCTextureUpdateQueue(); + + void appendFullUpload(TextureUploader::Parameters); + void appendPartialUpload(TextureUploader::Parameters); + void appendCopy(TextureCopier::Parameters); + + void clearUploads(); + + TextureUploader::Parameters takeFirstFullUpload(); + TextureUploader::Parameters takeFirstPartialUpload(); + TextureCopier::Parameters takeFirstCopy(); + + size_t fullUploadSize() const { return m_fullEntries.size(); } + size_t partialUploadSize() const { return m_partialEntries.size(); } + size_t copySize() const { return m_copyEntries.size(); } + + bool hasMoreUpdates() const; + +private: + Deque<TextureUploader::Parameters> m_fullEntries; + Deque<TextureUploader::Parameters> m_partialEntries; + Deque<TextureCopier::Parameters> m_copyEntries; +}; + +} + +#endif // CCTextureUpdateQueue_h diff --git a/cc/CCThread.h b/cc/CCThread.h new file mode 100644 index 0000000..d375932 --- /dev/null +++ b/cc/CCThread.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef CCThread_h +#define CCThread_h + +#include <wtf/PassOwnPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +// CCThread provides basic infrastructure for messaging with the compositor in a +// platform-neutral way. +class CCThread { +public: + virtual ~CCThread() { } + + class Task { + WTF_MAKE_NONCOPYABLE(Task); + public: + virtual ~Task() { } + virtual void performTask() = 0; + void* instance() const { return m_instance; } + protected: + Task(void* instance) : m_instance(instance) { } + void* m_instance; + }; + + // Executes the task on context's thread asynchronously. + virtual void postTask(PassOwnPtr<Task>) = 0; + + // Executes the task after the specified delay. + virtual void postDelayedTask(PassOwnPtr<Task>, long long delayMs) = 0; + + virtual WTF::ThreadIdentifier threadID() const = 0; +}; + +} + +#endif diff --git a/cc/CCThreadProxy.cpp b/cc/CCThreadProxy.cpp new file mode 100644 index 0000000..8d0a8b0 --- /dev/null +++ b/cc/CCThreadProxy.cpp @@ -0,0 +1,892 @@ +// 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 "config.h" + +#include "CCThreadProxy.h" + +#include "CCDelayBasedTimeSource.h" +#include "CCDrawQuad.h" +#include "CCFrameRateController.h" +#include "CCGraphicsContext.h" +#include "CCInputHandler.h" +#include "CCLayerTreeHost.h" +#include "CCScheduler.h" +#include "CCScopedThreadProxy.h" +#include "CCTextureUpdateController.h" +#include "CCThreadTask.h" +#include "TraceEvent.h" +#include <public/WebSharedGraphicsContext3D.h> +#include <wtf/CurrentTime.h> +#include <wtf/MainThread.h> + +using namespace WTF; + +using WebKit::WebSharedGraphicsContext3D; +namespace { + +// Measured in seconds. +static const double contextRecreationTickRate = 0.03; + +} // anonymous namespace + +namespace WebCore { + +namespace { + +// Type of texture uploader to use for texture updates. +static TextureUploaderOption textureUploader = ThrottledUploader; + +} // anonymous namespace + +PassOwnPtr<CCProxy> CCThreadProxy::create(CCLayerTreeHost* layerTreeHost) +{ + return adoptPtr(new CCThreadProxy(layerTreeHost)); +} + +CCThreadProxy::CCThreadProxy(CCLayerTreeHost* layerTreeHost) + : m_animateRequested(false) + , m_commitRequested(false) + , m_forcedCommitRequested(false) + , m_layerTreeHost(layerTreeHost) + , m_compositorIdentifier(-1) + , m_rendererInitialized(false) + , m_started(false) + , m_texturesAcquired(true) + , m_inCompositeAndReadback(false) + , m_mainThreadProxy(CCScopedThreadProxy::create(CCProxy::mainThread())) + , m_beginFrameCompletionEventOnImplThread(0) + , m_readbackRequestOnImplThread(0) + , m_commitCompletionEventOnImplThread(0) + , m_textureAcquisitionCompletionEventOnImplThread(0) + , m_resetContentsTexturesPurgedAfterCommitOnImplThread(false) + , m_nextFrameIsNewlyCommittedFrameOnImplThread(false) + , m_renderVSyncEnabled(layerTreeHost->settings().renderVSyncEnabled) +{ + TRACE_EVENT0("cc", "CCThreadProxy::CCThreadProxy"); + ASSERT(isMainThread()); +} + +CCThreadProxy::~CCThreadProxy() +{ + TRACE_EVENT0("cc", "CCThreadProxy::~CCThreadProxy"); + ASSERT(isMainThread()); + ASSERT(!m_started); +} + +bool CCThreadProxy::compositeAndReadback(void *pixels, const IntRect& rect) +{ + TRACE_EVENT0("cc", "CCThreadPRoxy::compositeAndReadback"); + ASSERT(isMainThread()); + ASSERT(m_layerTreeHost); + + if (!m_layerTreeHost->initializeRendererIfNeeded()) { + TRACE_EVENT0("cc", "compositeAndReadback_EarlyOut_LR_Uninitialized"); + return false; + } + + + // Perform a synchronous commit. + CCCompletionEvent beginFrameCompletion; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::forceBeginFrameOnImplThread, &beginFrameCompletion)); + beginFrameCompletion.wait(); + m_inCompositeAndReadback = true; + beginFrame(); + m_inCompositeAndReadback = false; + + // Perform a synchronous readback. + ReadbackRequest request; + request.rect = rect; + request.pixels = pixels; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::requestReadbackOnImplThread, &request)); + request.completion.wait(); + return request.success; +} + +void CCThreadProxy::requestReadbackOnImplThread(ReadbackRequest* request) +{ + ASSERT(CCProxy::isImplThread()); + ASSERT(!m_readbackRequestOnImplThread); + if (!m_layerTreeHostImpl) { + request->success = false; + request->completion.signal(); + return; + } + + m_readbackRequestOnImplThread = request; + m_schedulerOnImplThread->setNeedsRedraw(); + m_schedulerOnImplThread->setNeedsForcedRedraw(); +} + +void CCThreadProxy::startPageScaleAnimation(const IntSize& targetPosition, bool useAnchor, float scale, double duration) +{ + ASSERT(CCProxy::isMainThread()); + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::requestStartPageScaleAnimationOnImplThread, targetPosition, useAnchor, scale, duration)); +} + +void CCThreadProxy::requestStartPageScaleAnimationOnImplThread(IntSize targetPosition, bool useAnchor, float scale, double duration) +{ + ASSERT(CCProxy::isImplThread()); + if (m_layerTreeHostImpl) + m_layerTreeHostImpl->startPageScaleAnimation(targetPosition, useAnchor, scale, monotonicallyIncreasingTime(), duration); +} + +void CCThreadProxy::finishAllRendering() +{ + ASSERT(CCProxy::isMainThread()); + + // Make sure all GL drawing is finished on the impl thread. + CCCompletionEvent completion; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::finishAllRenderingOnImplThread, &completion)); + completion.wait(); +} + +bool CCThreadProxy::isStarted() const +{ + ASSERT(CCProxy::isMainThread()); + return m_started; +} + +bool CCThreadProxy::initializeContext() +{ + TRACE_EVENT0("cc", "CCThreadProxy::initializeContext"); + OwnPtr<CCGraphicsContext> context = m_layerTreeHost->createContext(); + if (!context) + return false; + + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::initializeContextOnImplThread, + context.leakPtr())); + return true; +} + +void CCThreadProxy::setSurfaceReady() +{ + TRACE_EVENT0("cc", "CCThreadProxy::setSurfaceReady"); + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::setSurfaceReadyOnImplThread)); +} + +void CCThreadProxy::setSurfaceReadyOnImplThread() +{ + TRACE_EVENT0("cc", "CCThreadProxy::setSurfaceReadyOnImplThread"); + m_schedulerOnImplThread->setCanBeginFrame(true); +} + +void CCThreadProxy::setVisible(bool visible) +{ + TRACE_EVENT0("cc", "CCThreadProxy::setVisible"); + CCCompletionEvent completion; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::setVisibleOnImplThread, &completion, visible)); + completion.wait(); +} + +void CCThreadProxy::setVisibleOnImplThread(CCCompletionEvent* completion, bool visible) +{ + TRACE_EVENT0("cc", "CCThreadProxy::setVisibleOnImplThread"); + m_layerTreeHostImpl->setVisible(visible); + m_schedulerOnImplThread->setVisible(visible); + completion->signal(); +} + +bool CCThreadProxy::initializeRenderer() +{ + TRACE_EVENT0("cc", "CCThreadProxy::initializeRenderer"); + // Make a blocking call to initializeRendererOnImplThread. The results of that call + // are pushed into the initializeSucceeded and capabilities local variables. + CCCompletionEvent completion; + bool initializeSucceeded = false; + RendererCapabilities capabilities; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::initializeRendererOnImplThread, + &completion, + &initializeSucceeded, + &capabilities)); + completion.wait(); + + if (initializeSucceeded) { + m_rendererInitialized = true; + m_RendererCapabilitiesMainThreadCopy = capabilities; + } + return initializeSucceeded; +} + +bool CCThreadProxy::recreateContext() +{ + TRACE_EVENT0("cc", "CCThreadProxy::recreateContext"); + ASSERT(isMainThread()); + + // Try to create the context. + OwnPtr<CCGraphicsContext> context = m_layerTreeHost->createContext(); + if (!context) + return false; + if (m_layerTreeHost->needsSharedContext()) + if (!WebSharedGraphicsContext3D::createCompositorThreadContext()) + return false; + + // Make a blocking call to recreateContextOnImplThread. The results of that + // call are pushed into the recreateSucceeded and capabilities local + // variables. + CCCompletionEvent completion; + bool recreateSucceeded = false; + RendererCapabilities capabilities; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::recreateContextOnImplThread, + &completion, + context.leakPtr(), + &recreateSucceeded, + &capabilities)); + completion.wait(); + + if (recreateSucceeded) + m_RendererCapabilitiesMainThreadCopy = capabilities; + return recreateSucceeded; +} + +int CCThreadProxy::compositorIdentifier() const +{ + ASSERT(isMainThread()); + return m_compositorIdentifier; +} + +void CCThreadProxy::implSideRenderingStats(CCRenderingStats& stats) +{ + ASSERT(isMainThread()); + + CCCompletionEvent completion; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::implSideRenderingStatsOnImplThread, + &completion, + &stats)); + completion.wait(); +} + +const RendererCapabilities& CCThreadProxy::rendererCapabilities() const +{ + ASSERT(m_rendererInitialized); + return m_RendererCapabilitiesMainThreadCopy; +} + +void CCThreadProxy::loseContext() +{ + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::didLoseContextOnImplThread)); +} + +void CCThreadProxy::setNeedsAnimate() +{ + ASSERT(isMainThread()); + if (m_animateRequested) + return; + + TRACE_EVENT0("cc", "CCThreadProxy::setNeedsAnimate"); + m_animateRequested = true; + setNeedsCommit(); +} + +void CCThreadProxy::setNeedsCommit() +{ + ASSERT(isMainThread()); + if (m_commitRequested) + return; + + TRACE_EVENT0("cc", "CCThreadProxy::setNeedsCommit"); + m_commitRequested = true; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::setNeedsCommitOnImplThread)); +} + +void CCThreadProxy::didLoseContextOnImplThread() +{ + ASSERT(isImplThread()); + TRACE_EVENT0("cc", "CCThreadProxy::didLoseContextOnImplThread"); + m_schedulerOnImplThread->didLoseContext(); +} + +void CCThreadProxy::onSwapBuffersCompleteOnImplThread() +{ + ASSERT(isImplThread()); + TRACE_EVENT0("cc", "CCThreadProxy::onSwapBuffersCompleteOnImplThread"); + m_schedulerOnImplThread->didSwapBuffersComplete(); + m_mainThreadProxy->postTask(createCCThreadTask(this, &CCThreadProxy::didCompleteSwapBuffers)); +} + +void CCThreadProxy::onVSyncParametersChanged(double monotonicTimebase, double intervalInSeconds) +{ + ASSERT(isImplThread()); + TRACE_EVENT0("cc", "CCThreadProxy::onVSyncParametersChanged"); + m_schedulerOnImplThread->setTimebaseAndInterval(monotonicTimebase, intervalInSeconds); +} + +void CCThreadProxy::setNeedsCommitOnImplThread() +{ + ASSERT(isImplThread()); + TRACE_EVENT0("cc", "CCThreadProxy::setNeedsCommitOnImplThread"); + m_schedulerOnImplThread->setNeedsCommit(); +} + +void CCThreadProxy::setNeedsForcedCommitOnImplThread() +{ + ASSERT(isImplThread()); + TRACE_EVENT0("cc", "CCThreadProxy::setNeedsForcedCommitOnImplThread"); + m_schedulerOnImplThread->setNeedsCommit(); + m_schedulerOnImplThread->setNeedsForcedCommit(); +} + +void CCThreadProxy::postAnimationEventsToMainThreadOnImplThread(PassOwnPtr<CCAnimationEventsVector> events, double wallClockTime) +{ + ASSERT(isImplThread()); + TRACE_EVENT0("cc", "CCThreadProxy::postAnimationEventsToMainThreadOnImplThread"); + m_mainThreadProxy->postTask(createCCThreadTask(this, &CCThreadProxy::setAnimationEvents, events, wallClockTime)); +} + +void CCThreadProxy::setNeedsRedraw() +{ + ASSERT(isMainThread()); + TRACE_EVENT0("cc", "CCThreadProxy::setNeedsRedraw"); + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::setFullRootLayerDamageOnImplThread)); + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::setNeedsRedrawOnImplThread)); +} + +bool CCThreadProxy::commitRequested() const +{ + ASSERT(isMainThread()); + return m_commitRequested; +} + +void CCThreadProxy::setNeedsRedrawOnImplThread() +{ + ASSERT(isImplThread()); + TRACE_EVENT0("cc", "CCThreadProxy::setNeedsRedrawOnImplThread"); + m_schedulerOnImplThread->setNeedsRedraw(); +} + +void CCThreadProxy::start() +{ + ASSERT(isMainThread()); + ASSERT(CCProxy::implThread()); + // Create LayerTreeHostImpl. + CCCompletionEvent completion; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::initializeImplOnImplThread, &completion)); + completion.wait(); + + m_started = true; +} + +void CCThreadProxy::stop() +{ + TRACE_EVENT0("cc", "CCThreadProxy::stop"); + ASSERT(isMainThread()); + ASSERT(m_started); + + // Synchronously deletes the impl. + { + DebugScopedSetMainThreadBlocked mainThreadBlocked; + + CCCompletionEvent completion; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::layerTreeHostClosedOnImplThread, &completion)); + completion.wait(); + } + + m_mainThreadProxy->shutdown(); // Stop running tasks posted to us. + + ASSERT(!m_layerTreeHostImpl); // verify that the impl deleted. + m_layerTreeHost = 0; + m_started = false; +} + +void CCThreadProxy::forceSerializeOnSwapBuffers() +{ + CCCompletionEvent completion; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::forceSerializeOnSwapBuffersOnImplThread, &completion)); + completion.wait(); +} + +void CCThreadProxy::forceSerializeOnSwapBuffersOnImplThread(CCCompletionEvent* completion) +{ + if (m_rendererInitialized) + m_layerTreeHostImpl->renderer()->doNoOp(); + completion->signal(); +} + + +void CCThreadProxy::finishAllRenderingOnImplThread(CCCompletionEvent* completion) +{ + TRACE_EVENT0("cc", "CCThreadProxy::finishAllRenderingOnImplThread"); + ASSERT(isImplThread()); + m_layerTreeHostImpl->finishAllRendering(); + completion->signal(); +} + +void CCThreadProxy::forceBeginFrameOnImplThread(CCCompletionEvent* completion) +{ + TRACE_EVENT0("cc", "CCThreadProxy::forceBeginFrameOnImplThread"); + ASSERT(!m_beginFrameCompletionEventOnImplThread); + + if (m_schedulerOnImplThread->commitPending()) { + completion->signal(); + return; + } + + m_beginFrameCompletionEventOnImplThread = completion; + setNeedsForcedCommitOnImplThread(); +} + +void CCThreadProxy::scheduledActionBeginFrame() +{ + TRACE_EVENT0("cc", "CCThreadProxy::scheduledActionBeginFrame"); + ASSERT(!m_pendingBeginFrameRequest); + m_pendingBeginFrameRequest = adoptPtr(new BeginFrameAndCommitState()); + m_pendingBeginFrameRequest->monotonicFrameBeginTime = monotonicallyIncreasingTime(); + m_pendingBeginFrameRequest->scrollInfo = m_layerTreeHostImpl->processScrollDeltas(); + m_pendingBeginFrameRequest->contentsTexturesWereDeleted = m_layerTreeHostImpl->contentsTexturesPurged(); + m_pendingBeginFrameRequest->memoryAllocationLimitBytes = m_layerTreeHostImpl->memoryAllocationLimitBytes(); + + m_mainThreadProxy->postTask(createCCThreadTask(this, &CCThreadProxy::beginFrame)); + + if (m_beginFrameCompletionEventOnImplThread) { + m_beginFrameCompletionEventOnImplThread->signal(); + m_beginFrameCompletionEventOnImplThread = 0; + } +} + +void CCThreadProxy::beginFrame() +{ + TRACE_EVENT0("cc", "CCThreadProxy::beginFrame"); + ASSERT(isMainThread()); + if (!m_layerTreeHost) + return; + + if (!m_pendingBeginFrameRequest) { + TRACE_EVENT0("cc", "EarlyOut_StaleBeginFrameMessage"); + return; + } + + if (m_layerTreeHost->needsSharedContext() && !WebSharedGraphicsContext3D::haveCompositorThreadContext()) + WebSharedGraphicsContext3D::createCompositorThreadContext(); + + OwnPtr<BeginFrameAndCommitState> request(m_pendingBeginFrameRequest.release()); + + // Do not notify the impl thread of commit requests that occur during + // the apply/animate/layout part of the beginFrameAndCommit process since + // those commit requests will get painted immediately. Once we have done + // the paint, m_commitRequested will be set to false to allow new commit + // requests to be scheduled. + m_commitRequested = true; + + // On the other hand, the animationRequested flag needs to be cleared + // here so that any animation requests generated by the apply or animate + // callbacks will trigger another frame. + m_animateRequested = false; + + // FIXME: technically, scroll deltas need to be applied for dropped commits as well. + // Re-do the commit flow so that we don't send the scrollInfo on the BFAC message. + m_layerTreeHost->applyScrollAndScale(*request->scrollInfo); + + if (!m_inCompositeAndReadback && !m_layerTreeHost->visible()) { + m_commitRequested = false; + m_forcedCommitRequested = false; + + TRACE_EVENT0("cc", "EarlyOut_NotVisible"); + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::beginFrameAbortedOnImplThread)); + return; + } + + m_layerTreeHost->willBeginFrame(); + + m_layerTreeHost->updateAnimations(request->monotonicFrameBeginTime); + m_layerTreeHost->layout(); + + // Clear the commit flag after updating animations and layout here --- objects that only + // layout when painted will trigger another setNeedsCommit inside + // updateLayers. + m_commitRequested = false; + m_forcedCommitRequested = false; + + if (!m_layerTreeHost->initializeRendererIfNeeded()) + return; + + if (request->contentsTexturesWereDeleted) + m_layerTreeHost->evictAllContentTextures(); + + OwnPtr<CCTextureUpdateQueue> queue = adoptPtr(new CCTextureUpdateQueue); + m_layerTreeHost->updateLayers(*(queue.get()), request->memoryAllocationLimitBytes); + + // Once single buffered layers are committed, they cannot be modified until + // they are drawn by the impl thread. + m_texturesAcquired = false; + + m_layerTreeHost->willCommit(); + // Before applying scrolls and calling animate, we set m_animateRequested to false. + // If it is true now, it means setNeedAnimate was called again. Call setNeedsCommit + // now so that we get begin frame when this one is done. + if (m_animateRequested) + setNeedsCommit(); + + // Notify the impl thread that the beginFrame has completed. This will + // begin the commit process, which is blocking from the main thread's + // point of view, but asynchronously performed on the impl thread, + // coordinated by the CCScheduler. + { + TRACE_EVENT0("cc", "commit"); + DebugScopedSetMainThreadBlocked mainThreadBlocked; + + CCCompletionEvent completion; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::beginFrameCompleteOnImplThread, &completion, queue.release(), request->contentsTexturesWereDeleted)); + completion.wait(); + } + + m_layerTreeHost->commitComplete(); + m_layerTreeHost->didBeginFrame(); +} + +void CCThreadProxy::beginFrameCompleteOnImplThread(CCCompletionEvent* completion, PassOwnPtr<CCTextureUpdateQueue> queue, bool contentsTexturesWereDeleted) +{ + TRACE_EVENT0("cc", "CCThreadProxy::beginFrameCompleteOnImplThread"); + ASSERT(!m_commitCompletionEventOnImplThread); + ASSERT(isImplThread()); + ASSERT(m_schedulerOnImplThread); + ASSERT(m_schedulerOnImplThread->commitPending()); + + if (!m_layerTreeHostImpl) { + completion->signal(); + return; + } + + if (!contentsTexturesWereDeleted && m_layerTreeHostImpl->contentsTexturesPurged()) { + // We purged the content textures on the impl thread between the time we + // posted the beginFrame task and now, meaning we have a bunch of + // uploads that are now invalid. Clear the uploads (they all go to + // content textures), and kick another commit to fill them again. + queue->clearUploads(); + setNeedsCommitOnImplThread(); + } else + m_resetContentsTexturesPurgedAfterCommitOnImplThread = true; + + m_currentTextureUpdateControllerOnImplThread = CCTextureUpdateController::create(CCProxy::implThread(), queue, m_layerTreeHostImpl->resourceProvider(), m_layerTreeHostImpl->renderer()->textureCopier(), m_layerTreeHostImpl->renderer()->textureUploader()); + m_commitCompletionEventOnImplThread = completion; + + m_schedulerOnImplThread->beginFrameComplete(); +} + +void CCThreadProxy::beginFrameAbortedOnImplThread() +{ + TRACE_EVENT0("cc", "CCThreadProxy::beginFrameAbortedOnImplThread"); + ASSERT(isImplThread()); + ASSERT(m_schedulerOnImplThread); + ASSERT(m_schedulerOnImplThread->commitPending()); + + m_schedulerOnImplThread->beginFrameAborted(); +} + +bool CCThreadProxy::hasMoreResourceUpdates() const +{ + if (!m_currentTextureUpdateControllerOnImplThread) + return false; + return m_currentTextureUpdateControllerOnImplThread->hasMoreUpdates(); +} + +bool CCThreadProxy::canDraw() +{ + ASSERT(isImplThread()); + if (!m_layerTreeHostImpl) + return false; + return m_layerTreeHostImpl->canDraw(); +} + +void CCThreadProxy::scheduledActionUpdateMoreResources(double monotonicTimeLimit) +{ + TRACE_EVENT0("cc", "CCThreadProxy::scheduledActionUpdateMoreResources"); + ASSERT(m_currentTextureUpdateControllerOnImplThread); + m_currentTextureUpdateControllerOnImplThread->updateMoreTextures(monotonicTimeLimit); +} + +void CCThreadProxy::scheduledActionCommit() +{ + TRACE_EVENT0("cc", "CCThreadProxy::scheduledActionCommit"); + ASSERT(isImplThread()); + ASSERT(m_currentTextureUpdateControllerOnImplThread); + ASSERT(!m_currentTextureUpdateControllerOnImplThread->hasMoreUpdates()); + ASSERT(m_commitCompletionEventOnImplThread); + + m_currentTextureUpdateControllerOnImplThread.clear(); + + m_layerTreeHostImpl->beginCommit(); + + m_layerTreeHost->beginCommitOnImplThread(m_layerTreeHostImpl.get()); + m_layerTreeHost->finishCommitOnImplThread(m_layerTreeHostImpl.get()); + + if (m_resetContentsTexturesPurgedAfterCommitOnImplThread) { + m_resetContentsTexturesPurgedAfterCommitOnImplThread = false; + m_layerTreeHostImpl->resetContentsTexturesPurged(); + } + + m_layerTreeHostImpl->commitComplete(); + + m_nextFrameIsNewlyCommittedFrameOnImplThread = true; + + m_commitCompletionEventOnImplThread->signal(); + m_commitCompletionEventOnImplThread = 0; + + // SetVisible kicks off the next scheduler action, so this must be last. + m_schedulerOnImplThread->setVisible(m_layerTreeHostImpl->visible()); +} + +void CCThreadProxy::scheduledActionBeginContextRecreation() +{ + ASSERT(isImplThread()); + m_mainThreadProxy->postTask(createCCThreadTask(this, &CCThreadProxy::beginContextRecreation)); +} + +CCScheduledActionDrawAndSwapResult CCThreadProxy::scheduledActionDrawAndSwapInternal(bool forcedDraw) +{ + TRACE_EVENT0("cc", "CCThreadProxy::scheduledActionDrawAndSwap"); + CCScheduledActionDrawAndSwapResult result; + result.didDraw = false; + result.didSwap = false; + ASSERT(isImplThread()); + ASSERT(m_layerTreeHostImpl); + if (!m_layerTreeHostImpl) + return result; + + ASSERT(m_layerTreeHostImpl->renderer()); + if (!m_layerTreeHostImpl->renderer()) + return result; + + // FIXME: compute the frame display time more intelligently + double monotonicTime = monotonicallyIncreasingTime(); + double wallClockTime = currentTime(); + + m_inputHandlerOnImplThread->animate(monotonicTime); + m_layerTreeHostImpl->animate(monotonicTime, wallClockTime); + + // This method is called on a forced draw, regardless of whether we are able to produce a frame, + // as the calling site on main thread is blocked until its request completes, and we signal + // completion here. If canDraw() is false, we will indicate success=false to the caller, but we + // must still signal completion to avoid deadlock. + + // We guard prepareToDraw() with canDraw() because it always returns a valid frame, so can only + // be used when such a frame is possible. Since drawLayers() depends on the result of + // prepareToDraw(), it is guarded on canDraw() as well. + + CCLayerTreeHostImpl::FrameData frame; + bool drawFrame = m_layerTreeHostImpl->canDraw() && (m_layerTreeHostImpl->prepareToDraw(frame) || forcedDraw); + if (drawFrame) { + m_layerTreeHostImpl->drawLayers(frame); + result.didDraw = true; + } + m_layerTreeHostImpl->didDrawAllLayers(frame); + + // Check for a pending compositeAndReadback. + if (m_readbackRequestOnImplThread) { + m_readbackRequestOnImplThread->success = false; + if (drawFrame) { + m_layerTreeHostImpl->readback(m_readbackRequestOnImplThread->pixels, m_readbackRequestOnImplThread->rect); + m_readbackRequestOnImplThread->success = !m_layerTreeHostImpl->isContextLost(); + } + m_readbackRequestOnImplThread->completion.signal(); + m_readbackRequestOnImplThread = 0; + } else if (drawFrame) + result.didSwap = m_layerTreeHostImpl->swapBuffers(); + + // Tell the main thread that the the newly-commited frame was drawn. + if (m_nextFrameIsNewlyCommittedFrameOnImplThread) { + m_nextFrameIsNewlyCommittedFrameOnImplThread = false; + m_mainThreadProxy->postTask(createCCThreadTask(this, &CCThreadProxy::didCommitAndDrawFrame)); + } + + return result; +} + +void CCThreadProxy::acquireLayerTextures() +{ + // Called when the main thread needs to modify a layer texture that is used + // directly by the compositor. + // This method will block until the next compositor draw if there is a + // previously committed frame that is still undrawn. This is necessary to + // ensure that the main thread does not monopolize access to the textures. + ASSERT(isMainThread()); + + if (m_texturesAcquired) + return; + + TRACE_EVENT0("cc", "CCThreadProxy::acquireLayerTextures"); + CCCompletionEvent completion; + CCProxy::implThread()->postTask(createCCThreadTask(this, &CCThreadProxy::acquireLayerTexturesForMainThreadOnImplThread, &completion)); + completion.wait(); // Block until it is safe to write to layer textures from the main thread. + + m_texturesAcquired = true; +} + +void CCThreadProxy::acquireLayerTexturesForMainThreadOnImplThread(CCCompletionEvent* completion) +{ + ASSERT(isImplThread()); + ASSERT(!m_textureAcquisitionCompletionEventOnImplThread); + + m_textureAcquisitionCompletionEventOnImplThread = completion; + m_schedulerOnImplThread->setMainThreadNeedsLayerTextures(); +} + +void CCThreadProxy::scheduledActionAcquireLayerTexturesForMainThread() +{ + ASSERT(m_textureAcquisitionCompletionEventOnImplThread); + m_textureAcquisitionCompletionEventOnImplThread->signal(); + m_textureAcquisitionCompletionEventOnImplThread = 0; +} + +CCScheduledActionDrawAndSwapResult CCThreadProxy::scheduledActionDrawAndSwapIfPossible() +{ + return scheduledActionDrawAndSwapInternal(false); +} + +CCScheduledActionDrawAndSwapResult CCThreadProxy::scheduledActionDrawAndSwapForced() +{ + return scheduledActionDrawAndSwapInternal(true); +} + +void CCThreadProxy::didCommitAndDrawFrame() +{ + ASSERT(isMainThread()); + if (!m_layerTreeHost) + return; + m_layerTreeHost->didCommitAndDrawFrame(); +} + +void CCThreadProxy::didCompleteSwapBuffers() +{ + ASSERT(isMainThread()); + if (!m_layerTreeHost) + return; + m_layerTreeHost->didCompleteSwapBuffers(); +} + +void CCThreadProxy::setAnimationEvents(PassOwnPtr<CCAnimationEventsVector> events, double wallClockTime) +{ + TRACE_EVENT0("cc", "CCThreadProxy::setAnimationEvents"); + ASSERT(isMainThread()); + if (!m_layerTreeHost) + return; + m_layerTreeHost->setAnimationEvents(events, wallClockTime); +} + +class CCThreadProxyContextRecreationTimer : public CCTimer, CCTimerClient { +public: + static PassOwnPtr<CCThreadProxyContextRecreationTimer> create(CCThreadProxy* proxy) { return adoptPtr(new CCThreadProxyContextRecreationTimer(proxy)); } + + virtual void onTimerFired() OVERRIDE + { + m_proxy->tryToRecreateContext(); + } + +private: + explicit CCThreadProxyContextRecreationTimer(CCThreadProxy* proxy) + : CCTimer(CCProxy::mainThread(), this) + , m_proxy(proxy) + { + } + + CCThreadProxy* m_proxy; +}; + +void CCThreadProxy::beginContextRecreation() +{ + TRACE_EVENT0("cc", "CCThreadProxy::beginContextRecreation"); + ASSERT(isMainThread()); + ASSERT(!m_contextRecreationTimer); + m_contextRecreationTimer = CCThreadProxyContextRecreationTimer::create(this); + m_layerTreeHost->didLoseContext(); + m_contextRecreationTimer->startOneShot(contextRecreationTickRate); +} + +void CCThreadProxy::tryToRecreateContext() +{ + ASSERT(isMainThread()); + ASSERT(m_layerTreeHost); + CCLayerTreeHost::RecreateResult result = m_layerTreeHost->recreateContext(); + if (result == CCLayerTreeHost::RecreateFailedButTryAgain) + m_contextRecreationTimer->startOneShot(contextRecreationTickRate); + else if (result == CCLayerTreeHost::RecreateSucceeded) + m_contextRecreationTimer.clear(); +} + +void CCThreadProxy::initializeImplOnImplThread(CCCompletionEvent* completion) +{ + TRACE_EVENT0("cc", "CCThreadProxy::initializeImplOnImplThread"); + ASSERT(isImplThread()); + m_layerTreeHostImpl = m_layerTreeHost->createLayerTreeHostImpl(this); + const double displayRefreshInterval = 1.0 / 60.0; + OwnPtr<CCFrameRateController> frameRateController; + if (m_renderVSyncEnabled) + frameRateController = adoptPtr(new CCFrameRateController(CCDelayBasedTimeSource::create(displayRefreshInterval, CCProxy::implThread()))); + else + frameRateController = adoptPtr(new CCFrameRateController(CCProxy::implThread())); + m_schedulerOnImplThread = CCScheduler::create(this, frameRateController.release()); + m_schedulerOnImplThread->setVisible(m_layerTreeHostImpl->visible()); + + m_inputHandlerOnImplThread = CCInputHandler::create(m_layerTreeHostImpl.get()); + m_compositorIdentifier = m_inputHandlerOnImplThread->identifier(); + + completion->signal(); +} + +void CCThreadProxy::initializeContextOnImplThread(CCGraphicsContext* context) +{ + TRACE_EVENT0("cc", "CCThreadProxy::initializeContextOnImplThread"); + ASSERT(isImplThread()); + m_contextBeforeInitializationOnImplThread = adoptPtr(context); +} + +void CCThreadProxy::initializeRendererOnImplThread(CCCompletionEvent* completion, bool* initializeSucceeded, RendererCapabilities* capabilities) +{ + TRACE_EVENT0("cc", "CCThreadProxy::initializeRendererOnImplThread"); + ASSERT(isImplThread()); + ASSERT(m_contextBeforeInitializationOnImplThread); + *initializeSucceeded = m_layerTreeHostImpl->initializeRenderer(m_contextBeforeInitializationOnImplThread.release(), textureUploader); + if (*initializeSucceeded) { + *capabilities = m_layerTreeHostImpl->rendererCapabilities(); + if (capabilities->usingSwapCompleteCallback) + m_schedulerOnImplThread->setMaxFramesPending(2); + } + + completion->signal(); +} + +void CCThreadProxy::layerTreeHostClosedOnImplThread(CCCompletionEvent* completion) +{ + TRACE_EVENT0("cc", "CCThreadProxy::layerTreeHostClosedOnImplThread"); + ASSERT(isImplThread()); + if (!m_layerTreeHostImpl->contentsTexturesPurged()) + m_layerTreeHost->deleteContentsTexturesOnImplThread(m_layerTreeHostImpl->resourceProvider()); + m_inputHandlerOnImplThread.clear(); + m_layerTreeHostImpl.clear(); + m_schedulerOnImplThread.clear(); + completion->signal(); +} + +void CCThreadProxy::setFullRootLayerDamageOnImplThread() +{ + ASSERT(isImplThread()); + m_layerTreeHostImpl->setFullRootLayerDamage(); +} + +size_t CCThreadProxy::maxPartialTextureUpdates() const +{ + return CCTextureUpdateController::maxPartialTextureUpdates(); +} + +void CCThreadProxy::recreateContextOnImplThread(CCCompletionEvent* completion, CCGraphicsContext* contextPtr, bool* recreateSucceeded, RendererCapabilities* capabilities) +{ + TRACE_EVENT0("cc", "CCThreadProxy::recreateContextOnImplThread"); + ASSERT(isImplThread()); + if (!m_layerTreeHostImpl->contentsTexturesPurged()) + m_layerTreeHost->deleteContentsTexturesOnImplThread(m_layerTreeHostImpl->resourceProvider()); + *recreateSucceeded = m_layerTreeHostImpl->initializeRenderer(adoptPtr(contextPtr), textureUploader); + if (*recreateSucceeded) { + *capabilities = m_layerTreeHostImpl->rendererCapabilities(); + m_schedulerOnImplThread->didRecreateContext(); + } + completion->signal(); +} + +void CCThreadProxy::implSideRenderingStatsOnImplThread(CCCompletionEvent* completion, CCRenderingStats* stats) +{ + ASSERT(isImplThread()); + m_layerTreeHostImpl->renderingStats(*stats); + completion->signal(); +} + +} // namespace WebCore diff --git a/cc/CCThreadProxy.h b/cc/CCThreadProxy.h new file mode 100644 index 0000000..cc80ebd --- /dev/null +++ b/cc/CCThreadProxy.h @@ -0,0 +1,180 @@ +// 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. + +#ifndef CCThreadProxy_h +#define CCThreadProxy_h + +#include "CCAnimationEvents.h" +#include "CCCompletionEvent.h" +#include "CCLayerTreeHostImpl.h" +#include "CCProxy.h" +#include "CCScheduler.h" +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class CCInputHandler; +class CCLayerTreeHost; +class CCScheduler; +class CCScopedThreadProxy; +class CCTextureUpdateQueue; +class CCTextureUpdateController; +class CCThread; +class CCThreadProxyContextRecreationTimer; + +class CCThreadProxy : public CCProxy, CCLayerTreeHostImplClient, CCSchedulerClient { +public: + static PassOwnPtr<CCProxy> create(CCLayerTreeHost*); + + virtual ~CCThreadProxy(); + + // CCProxy implementation + virtual bool compositeAndReadback(void *pixels, const IntRect&) OVERRIDE; + virtual void startPageScaleAnimation(const IntSize& targetPosition, bool useAnchor, float scale, double duration) OVERRIDE; + virtual void finishAllRendering() OVERRIDE; + virtual bool isStarted() const OVERRIDE; + virtual bool initializeContext() OVERRIDE; + virtual void setSurfaceReady() OVERRIDE; + virtual void setVisible(bool) OVERRIDE; + virtual bool initializeRenderer() OVERRIDE; + virtual bool recreateContext() OVERRIDE; + virtual int compositorIdentifier() const OVERRIDE; + virtual void implSideRenderingStats(CCRenderingStats&) OVERRIDE; + virtual const RendererCapabilities& rendererCapabilities() const OVERRIDE; + virtual void loseContext() OVERRIDE; + virtual void setNeedsAnimate() OVERRIDE; + virtual void setNeedsCommit() OVERRIDE; + virtual void setNeedsRedraw() OVERRIDE; + virtual bool commitRequested() const OVERRIDE; + virtual void didAddAnimation() OVERRIDE { } + virtual void start() OVERRIDE; + virtual void stop() OVERRIDE; + virtual size_t maxPartialTextureUpdates() const OVERRIDE; + virtual void acquireLayerTextures() OVERRIDE; + virtual void forceSerializeOnSwapBuffers() OVERRIDE; + + // CCLayerTreeHostImplClient implementation + virtual void didLoseContextOnImplThread() OVERRIDE; + virtual void onSwapBuffersCompleteOnImplThread() OVERRIDE; + virtual void onVSyncParametersChanged(double monotonicTimebase, double intervalInSeconds) OVERRIDE; + virtual void setNeedsRedrawOnImplThread() OVERRIDE; + virtual void setNeedsCommitOnImplThread() OVERRIDE; + virtual void postAnimationEventsToMainThreadOnImplThread(PassOwnPtr<CCAnimationEventsVector>, double wallClockTime) OVERRIDE; + + // CCSchedulerClient implementation + virtual bool canDraw() OVERRIDE; + virtual bool hasMoreResourceUpdates() const OVERRIDE; + virtual void scheduledActionBeginFrame() OVERRIDE; + virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapIfPossible() OVERRIDE; + virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapForced() OVERRIDE; + virtual void scheduledActionUpdateMoreResources(double monotonicTimeLimit) OVERRIDE; + virtual void scheduledActionCommit() OVERRIDE; + virtual void scheduledActionBeginContextRecreation() OVERRIDE; + virtual void scheduledActionAcquireLayerTexturesForMainThread() OVERRIDE; + +private: + explicit CCThreadProxy(CCLayerTreeHost*); + friend class CCThreadProxyContextRecreationTimer; + + // Set on impl thread, read on main thread. + struct BeginFrameAndCommitState { + BeginFrameAndCommitState() + : monotonicFrameBeginTime(0) + { + } + + double monotonicFrameBeginTime; + OwnPtr<CCScrollAndScaleSet> scrollInfo; + bool contentsTexturesWereDeleted; + size_t memoryAllocationLimitBytes; + }; + OwnPtr<BeginFrameAndCommitState> m_pendingBeginFrameRequest; + + // Called on main thread + void beginFrame(); + void didCommitAndDrawFrame(); + void didCompleteSwapBuffers(); + void setAnimationEvents(PassOwnPtr<CCAnimationEventsVector>, double wallClockTime); + void beginContextRecreation(); + void tryToRecreateContext(); + + // Called on impl thread + struct ReadbackRequest { + CCCompletionEvent completion; + bool success; + void* pixels; + IntRect rect; + }; + void forceBeginFrameOnImplThread(CCCompletionEvent*); + void beginFrameCompleteOnImplThread(CCCompletionEvent*, PassOwnPtr<CCTextureUpdateQueue>, bool contentsTexturesWereDeleted); + void beginFrameAbortedOnImplThread(); + void requestReadbackOnImplThread(ReadbackRequest*); + void requestStartPageScaleAnimationOnImplThread(IntSize targetPosition, bool useAnchor, float scale, double durationSec); + void finishAllRenderingOnImplThread(CCCompletionEvent*); + void initializeImplOnImplThread(CCCompletionEvent*); + void setSurfaceReadyOnImplThread(); + void setVisibleOnImplThread(CCCompletionEvent*, bool); + void initializeContextOnImplThread(CCGraphicsContext*); + void initializeRendererOnImplThread(CCCompletionEvent*, bool* initializeSucceeded, RendererCapabilities*); + void layerTreeHostClosedOnImplThread(CCCompletionEvent*); + void setFullRootLayerDamageOnImplThread(); + void acquireLayerTexturesForMainThreadOnImplThread(CCCompletionEvent*); + void recreateContextOnImplThread(CCCompletionEvent*, CCGraphicsContext*, bool* recreateSucceeded, RendererCapabilities*); + void implSideRenderingStatsOnImplThread(CCCompletionEvent*, CCRenderingStats*); + CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapInternal(bool forcedDraw); + void forceSerializeOnSwapBuffersOnImplThread(CCCompletionEvent*); + void setNeedsForcedCommitOnImplThread(); + + // Accessed on main thread only. + bool m_animateRequested; + bool m_commitRequested; + bool m_forcedCommitRequested; + OwnPtr<CCThreadProxyContextRecreationTimer> m_contextRecreationTimer; + CCLayerTreeHost* m_layerTreeHost; + int m_compositorIdentifier; + bool m_rendererInitialized; + RendererCapabilities m_RendererCapabilitiesMainThreadCopy; + bool m_started; + bool m_texturesAcquired; + bool m_inCompositeAndReadback; + + OwnPtr<CCLayerTreeHostImpl> m_layerTreeHostImpl; + + OwnPtr<CCInputHandler> m_inputHandlerOnImplThread; + + OwnPtr<CCScheduler> m_schedulerOnImplThread; + + RefPtr<CCScopedThreadProxy> m_mainThreadProxy; + + // Holds on to the context we might use for compositing in between initializeContext() + // and initializeRenderer() calls. + OwnPtr<CCGraphicsContext> m_contextBeforeInitializationOnImplThread; + + // Set when the main thread is waiting on a scheduledActionBeginFrame to be issued. + CCCompletionEvent* m_beginFrameCompletionEventOnImplThread; + + // Set when the main thread is waiting on a readback. + ReadbackRequest* m_readbackRequestOnImplThread; + + // Set when the main thread is waiting on a commit to complete. + CCCompletionEvent* m_commitCompletionEventOnImplThread; + + // Set when the main thread is waiting on layers to be drawn. + CCCompletionEvent* m_textureAcquisitionCompletionEventOnImplThread; + + OwnPtr<CCTextureUpdateController> m_currentTextureUpdateControllerOnImplThread; + + // Set when we need to reset the contentsTexturesPurged flag after the + // commit. + bool m_resetContentsTexturesPurgedAfterCommitOnImplThread; + + // Set when the next draw should post didCommitAndDrawFrame to the main thread. + bool m_nextFrameIsNewlyCommittedFrameOnImplThread; + + bool m_renderVSyncEnabled; +}; + +} + +#endif diff --git a/cc/CCThreadTask.h b/cc/CCThreadTask.h new file mode 100644 index 0000000..c9d843e --- /dev/null +++ b/cc/CCThreadTask.h @@ -0,0 +1,305 @@ +// 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. +#ifndef CCThreadTask_h +#define CCThreadTask_h + +#include "CCThread.h" +#include <wtf/PassOwnPtr.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +template<typename T> +class CCThreadTask0 : public CCThread::Task { +public: + typedef void (T::*Method)(); + typedef CCThreadTask0<T> CCThreadTaskImpl; + + static PassOwnPtr<CCThreadTaskImpl> create(T* instance, Method method) + { + return adoptPtr(new CCThreadTaskImpl(instance, method)); + } + +private: + CCThreadTask0(T* instance, Method method) + : CCThread::Task(instance) + , m_method(method) + { + } + + virtual void performTask() OVERRIDE + { + (*static_cast<T*>(instance()).*m_method)(); + } + +private: + Method m_method; +}; + +template<typename T, typename P1, typename MP1> +class CCThreadTask1 : public CCThread::Task { +public: + typedef void (T::*Method)(MP1); + typedef CCThreadTask1<T, P1, MP1> CCThreadTaskImpl; + + static PassOwnPtr<CCThreadTaskImpl> create(T* instance, Method method, P1 parameter1) + { + return adoptPtr(new CCThreadTaskImpl(instance, method, parameter1)); + } + +private: + CCThreadTask1(T* instance, Method method, P1 parameter1) + : CCThread::Task(instance) + , m_method(method) + , m_parameter1(parameter1) + { + } + + virtual void performTask() OVERRIDE + { + (*static_cast<T*>(instance()).*m_method)(m_parameter1); + } + +private: + Method m_method; + P1 m_parameter1; +}; + +template<typename T, typename P1, typename MP1, typename P2, typename MP2> +class CCThreadTask2 : public CCThread::Task { +public: + typedef void (T::*Method)(MP1, MP2); + typedef CCThreadTask2<T, P1, MP1, P2, MP2> CCThreadTaskImpl; + + static PassOwnPtr<CCThreadTaskImpl> create(T* instance, Method method, P1 parameter1, P2 parameter2) + { + return adoptPtr(new CCThreadTaskImpl(instance, method, parameter1, parameter2)); + } + +private: + CCThreadTask2(T* instance, Method method, P1 parameter1, P2 parameter2) + : CCThread::Task(instance) + , m_method(method) + , m_parameter1(parameter1) + , m_parameter2(parameter2) + { + } + + virtual void performTask() OVERRIDE + { + (*static_cast<T*>(instance()).*m_method)(m_parameter1, m_parameter2); + } + +private: + Method m_method; + P1 m_parameter1; + P2 m_parameter2; +}; + +template<typename T, typename P1, typename MP1, typename P2, typename MP2, typename P3, typename MP3> +class CCThreadTask3 : public CCThread::Task { +public: + typedef void (T::*Method)(MP1, MP2, MP3); + typedef CCThreadTask3<T, P1, MP1, P2, MP2, P3, MP3> CCThreadTaskImpl; + + static PassOwnPtr<CCThreadTaskImpl> create(T* instance, Method method, P1 parameter1, P2 parameter2, P3 parameter3) + { + return adoptPtr(new CCThreadTaskImpl(instance, method, parameter1, parameter2, parameter3)); + } + +private: + CCThreadTask3(T* instance, Method method, P1 parameter1, P2 parameter2, P3 parameter3) + : CCThread::Task(instance) + , m_method(method) + , m_parameter1(parameter1) + , m_parameter2(parameter2) + , m_parameter3(parameter3) + { + } + + virtual void performTask() OVERRIDE + { + (*static_cast<T*>(instance()).*m_method)(m_parameter1, m_parameter2, m_parameter3); + } + +private: + Method m_method; + P1 m_parameter1; + P2 m_parameter2; + P3 m_parameter3; +}; + + +template<typename T, typename P1, typename MP1, typename P2, typename MP2, typename P3, typename MP3, typename P4, typename MP4> +class CCThreadTask4 : public CCThread::Task { +public: + typedef void (T::*Method)(MP1, MP2, MP3, MP4); + typedef CCThreadTask4<T, P1, MP1, P2, MP2, P3, MP3, P4, MP4> CCThreadTaskImpl; + + static PassOwnPtr<CCThreadTaskImpl> create(T* instance, Method method, P1 parameter1, P2 parameter2, P3 parameter3, P4 parameter4) + { + return adoptPtr(new CCThreadTaskImpl(instance, method, parameter1, parameter2, parameter3, parameter4)); + } + +private: + CCThreadTask4(T* instance, Method method, P1 parameter1, P2 parameter2, P3 parameter3, P4 parameter4) + : CCThread::Task(instance) + , m_method(method) + , m_parameter1(parameter1) + , m_parameter2(parameter2) + , m_parameter3(parameter3) + , m_parameter4(parameter4) + { + } + + virtual void performTask() OVERRIDE + { + (*static_cast<T*>(instance()).*m_method)(m_parameter1, m_parameter2, m_parameter3, m_parameter4); + } + +private: + Method m_method; + P1 m_parameter1; + P2 m_parameter2; + P3 m_parameter3; + P4 m_parameter4; +}; + +template<typename T, typename P1, typename MP1, typename P2, typename MP2, typename P3, typename MP3, typename P4, typename MP4, typename P5, typename MP5> +class CCThreadTask5 : public CCThread::Task { +public: + typedef void (T::*Method)(MP1, MP2, MP3, MP4, MP5); + typedef CCThreadTask5<T, P1, MP1, P2, MP2, P3, MP3, P4, MP4, P5, MP5> CCThreadTaskImpl; + + static PassOwnPtr<CCThreadTaskImpl> create(T* instance, Method method, P1 parameter1, P2 parameter2, P3 parameter3, P4 parameter4, P5 parameter5) + { + return adoptPtr(new CCThreadTaskImpl(instance, method, parameter1, parameter2, parameter3, parameter4, parameter5)); + } + +private: + CCThreadTask5(T* instance, Method method, P1 parameter1, P2 parameter2, P3 parameter3, P4 parameter4, P5 parameter5) + : CCThread::Task(instance) + , m_method(method) + , m_parameter1(parameter1) + , m_parameter2(parameter2) + , m_parameter3(parameter3) + , m_parameter4(parameter4) + , m_parameter5(parameter5) + { + } + + virtual void performTask() OVERRIDE + { + (*static_cast<T*>(instance()).*m_method)(m_parameter1, m_parameter2, m_parameter3, m_parameter4, m_parameter5); + } + +private: + Method m_method; + P1 m_parameter1; + P2 m_parameter2; + P3 m_parameter3; + P4 m_parameter4; + P5 m_parameter5; +}; + +template<typename T> +PassOwnPtr<CCThread::Task> createCCThreadTask( + T* const callee, + void (T::*method)()); + +template<typename T> +PassOwnPtr<CCThread::Task> createCCThreadTask( + T* const callee, + void (T::*method)()) +{ + return CCThreadTask0<T>::create( + callee, + method); +} + +template<typename T, typename P1, typename MP1> +PassOwnPtr<CCThread::Task> createCCThreadTask( + T* const callee, + void (T::*method)(MP1), + const P1& parameter1) +{ + return CCThreadTask1<T, P1, MP1>::create( + callee, + method, + parameter1); +} + +template<typename T, typename P1, typename MP1, typename P2, typename MP2> +PassOwnPtr<CCThread::Task> createCCThreadTask( + T* const callee, + void (T::*method)(MP1, MP2), + const P1& parameter1, + const P2& parameter2) +{ + return CCThreadTask2<T, P1, MP1, P2, MP2>::create( + callee, + method, + parameter1, + parameter2); +} + +template<typename T, typename P1, typename MP1, typename P2, typename MP2, typename P3, typename MP3> +PassOwnPtr<CCThread::Task> createCCThreadTask( + T* const callee, + void (T::*method)(MP1, MP2, MP3), + const P1& parameter1, + const P2& parameter2, + const P3& parameter3) +{ + return CCThreadTask3<T, P1, MP1, P2, MP2, P3, MP3>::create( + callee, + method, + parameter1, + parameter2, + parameter3); +} + +template<typename T, typename P1, typename MP1, typename P2, typename MP2, typename P3, typename MP3, typename P4, typename MP4> +PassOwnPtr<CCThread::Task> createCCThreadTask( + T* const callee, + void (T::*method)(MP1, MP2, MP3, MP4), + const P1& parameter1, + const P2& parameter2, + const P3& parameter3, + const P4& parameter4) +{ + return CCThreadTask4<T, P1, MP1, P2, MP2, P3, MP3, P4, MP4>::create( + callee, + method, + parameter1, + parameter2, + parameter3, + parameter4); + +} + +template<typename T, typename P1, typename MP1, typename P2, typename MP2, typename P3, typename MP3, typename P4, typename MP4, typename P5, typename MP5> +PassOwnPtr<CCThread::Task> createCCThreadTask( + T* const callee, + void (T::*method)(MP1, MP2, MP3, MP4, MP5), + const P1& parameter1, + const P2& parameter2, + const P3& parameter3, + const P4& parameter4, + const P5& parameter5) +{ + return CCThreadTask5<T, P1, MP1, P2, MP2, P3, MP3, P4, MP4, P5, MP5>::create( + callee, + method, + parameter1, + parameter2, + parameter3, + parameter4, + parameter5); + +} + +} // namespace WebCore + +#endif // CCThreadTask_h diff --git a/cc/CCThreadTaskTest.cpp b/cc/CCThreadTaskTest.cpp new file mode 100644 index 0000000..7a757ef --- /dev/null +++ b/cc/CCThreadTaskTest.cpp @@ -0,0 +1,45 @@ +// 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 "config.h" + +#include "CCThreadTask.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using namespace WTF; +using namespace WebCore; + +namespace { + +class Mock { +public: + MOCK_METHOD0(method0, void()); + MOCK_METHOD1(method1, void(int a1)); + MOCK_METHOD2(method2, void(int a1, int a2)); + MOCK_METHOD3(method3, void(int a1, int a2, int a3)); + MOCK_METHOD4(method4, void(int a1, int a2, int a3, int a4)); + MOCK_METHOD5(method5, void(int a1, int a2, int a3, int a4, int a5)); +}; + +TEST(CCThreadTaskTest, runnableMethods) +{ + Mock mock; + EXPECT_CALL(mock, method0()).Times(1); + EXPECT_CALL(mock, method1(9)).Times(1); + EXPECT_CALL(mock, method2(9, 8)).Times(1); + EXPECT_CALL(mock, method3(9, 8, 7)).Times(1); + EXPECT_CALL(mock, method4(9, 8, 7, 6)).Times(1); + EXPECT_CALL(mock, method5(9, 8, 7, 6, 5)).Times(1); + + createCCThreadTask(&mock, &Mock::method0)->performTask(); + createCCThreadTask(&mock, &Mock::method1, 9)->performTask(); + createCCThreadTask(&mock, &Mock::method2, 9, 8)->performTask(); + createCCThreadTask(&mock, &Mock::method3, 9, 8, 7)->performTask(); + createCCThreadTask(&mock, &Mock::method4, 9, 8, 7, 6)->performTask(); + createCCThreadTask(&mock, &Mock::method5, 9, 8, 7, 6, 5)->performTask(); +} + +} // namespace diff --git a/cc/CCThreadedTest.cpp b/cc/CCThreadedTest.cpp new file mode 100644 index 0000000..09526ac --- /dev/null +++ b/cc/CCThreadedTest.cpp @@ -0,0 +1,622 @@ +// 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 "config.h" + +#include "CCThreadedTest.h" + +#include "CCActiveAnimation.h" +#include "CCAnimationTestCommon.h" +#include "CCLayerAnimationController.h" +#include "CCLayerImpl.h" +#include "CCLayerTreeHostImpl.h" +#include "CCOcclusionTrackerTestCommon.h" +#include "CCScopedThreadProxy.h" +#include "CCSingleThreadProxy.h" +#include "CCTextureUpdateQueue.h" +#include "CCThreadTask.h" +#include "CCTiledLayerTestCommon.h" +#include "CCTimingFunction.h" +#include "ContentLayerChromium.h" +#include "FakeWebCompositorOutputSurface.h" +#include "FakeWebGraphicsContext3D.h" +#include "LayerChromium.h" +#include <gmock/gmock.h> +#include <public/Platform.h> +#include <public/WebCompositor.h> +#include <public/WebFilterOperation.h> +#include <public/WebFilterOperations.h> +#include <public/WebThread.h> +#include <wtf/Locker.h> +#include <wtf/MainThread.h> +#include <wtf/PassRefPtr.h> +#include <wtf/ThreadingPrimitives.h> +#include <wtf/Vector.h> + +using namespace WebCore; +using namespace WebKit; +using namespace WTF; + +namespace WebKitTests { + +PassOwnPtr<CompositorFakeWebGraphicsContext3DWithTextureTracking> CompositorFakeWebGraphicsContext3DWithTextureTracking::create(Attributes attrs) +{ + return adoptPtr(new CompositorFakeWebGraphicsContext3DWithTextureTracking(attrs)); +} + +WebGLId CompositorFakeWebGraphicsContext3DWithTextureTracking::createTexture() +{ + WebGLId texture = m_textures.size() + 1; + m_textures.append(texture); + return texture; +} + +void CompositorFakeWebGraphicsContext3DWithTextureTracking::deleteTexture(WebGLId texture) +{ + for (size_t i = 0; i < m_textures.size(); i++) { + if (m_textures[i] == texture) { + m_textures.remove(i); + break; + } + } +} + +void CompositorFakeWebGraphicsContext3DWithTextureTracking::bindTexture(WGC3Denum /* target */, WebGLId texture) +{ + m_usedTextures.add(texture); +} + +int CompositorFakeWebGraphicsContext3DWithTextureTracking::numTextures() const { return static_cast<int>(m_textures.size()); } +int CompositorFakeWebGraphicsContext3DWithTextureTracking::texture(int i) const { return m_textures[i]; } +void CompositorFakeWebGraphicsContext3DWithTextureTracking::resetTextures() { m_textures.clear(); } + +int CompositorFakeWebGraphicsContext3DWithTextureTracking::numUsedTextures() const { return static_cast<int>(m_usedTextures.size()); } +bool CompositorFakeWebGraphicsContext3DWithTextureTracking::usedTexture(int texture) const { return m_usedTextures.find(texture) != m_usedTextures.end(); } +void CompositorFakeWebGraphicsContext3DWithTextureTracking::resetUsedTextures() { m_usedTextures.clear(); } + +CompositorFakeWebGraphicsContext3DWithTextureTracking::CompositorFakeWebGraphicsContext3DWithTextureTracking(Attributes attrs) : CompositorFakeWebGraphicsContext3D(attrs) +{ +} + +PassOwnPtr<WebCompositorOutputSurface> TestHooks::createOutputSurface() +{ + return FakeWebCompositorOutputSurface::create(CompositorFakeWebGraphicsContext3DWithTextureTracking::create(WebGraphicsContext3D::Attributes())); +} + +PassOwnPtr<MockLayerTreeHostImpl> MockLayerTreeHostImpl::create(TestHooks* testHooks, const CCLayerTreeSettings& settings, CCLayerTreeHostImplClient* client) +{ + return adoptPtr(new MockLayerTreeHostImpl(testHooks, settings, client)); +} + +void MockLayerTreeHostImpl::beginCommit() +{ + CCLayerTreeHostImpl::beginCommit(); + m_testHooks->beginCommitOnCCThread(this); +} + +void MockLayerTreeHostImpl::commitComplete() +{ + CCLayerTreeHostImpl::commitComplete(); + m_testHooks->commitCompleteOnCCThread(this); +} + +bool MockLayerTreeHostImpl::prepareToDraw(FrameData& frame) +{ + bool result = CCLayerTreeHostImpl::prepareToDraw(frame); + if (!m_testHooks->prepareToDrawOnCCThread(this)) + result = false; + return result; +} + +void MockLayerTreeHostImpl::drawLayers(const FrameData& frame) +{ + CCLayerTreeHostImpl::drawLayers(frame); + m_testHooks->drawLayersOnCCThread(this); +} + +void MockLayerTreeHostImpl::animateLayers(double monotonicTime, double wallClockTime) +{ + m_testHooks->willAnimateLayers(this, monotonicTime); + CCLayerTreeHostImpl::animateLayers(monotonicTime, wallClockTime); + m_testHooks->animateLayers(this, monotonicTime); +} + +double MockLayerTreeHostImpl::lowFrequencyAnimationInterval() const +{ + return 1.0 / 60; +} + +MockLayerTreeHostImpl::MockLayerTreeHostImpl(TestHooks* testHooks, const CCLayerTreeSettings& settings, CCLayerTreeHostImplClient* client) + : CCLayerTreeHostImpl(settings, client) + , m_testHooks(testHooks) +{ +} + +// Adapts CCLayerTreeHost for test. Injects MockLayerTreeHostImpl. +class MockLayerTreeHost : public WebCore::CCLayerTreeHost { +public: + static PassOwnPtr<MockLayerTreeHost> create(TestHooks* testHooks, WebCore::CCLayerTreeHostClient* client, PassRefPtr<WebCore::LayerChromium> rootLayer, const WebCore::CCLayerTreeSettings& settings) + { + OwnPtr<MockLayerTreeHost> layerTreeHost(adoptPtr(new MockLayerTreeHost(testHooks, client, settings))); + bool success = layerTreeHost->initialize(); + EXPECT_TRUE(success); + layerTreeHost->setRootLayer(rootLayer); + + // LayerTreeHostImpl won't draw if it has 1x1 viewport. + layerTreeHost->setViewportSize(IntSize(1, 1), IntSize(1, 1)); + + layerTreeHost->rootLayer()->setLayerAnimationDelegate(testHooks); + + return layerTreeHost.release(); + } + + virtual PassOwnPtr<WebCore::CCLayerTreeHostImpl> createLayerTreeHostImpl(WebCore::CCLayerTreeHostImplClient* client) + { + return MockLayerTreeHostImpl::create(m_testHooks, settings(), client); + } + + virtual void didAddAnimation() OVERRIDE + { + CCLayerTreeHost::didAddAnimation(); + m_testHooks->didAddAnimation(); + } + +private: + MockLayerTreeHost(TestHooks* testHooks, WebCore::CCLayerTreeHostClient* client, const WebCore::CCLayerTreeSettings& settings) + : CCLayerTreeHost(client, settings) + , m_testHooks(testHooks) + { + } + + TestHooks* m_testHooks; +}; + +// Implementation of CCLayerTreeHost callback interface. +class MockLayerTreeHostClient : public MockCCLayerTreeHostClient { +public: + static PassOwnPtr<MockLayerTreeHostClient> create(TestHooks* testHooks) + { + return adoptPtr(new MockLayerTreeHostClient(testHooks)); + } + + virtual void willBeginFrame() OVERRIDE + { + } + + virtual void didBeginFrame() OVERRIDE + { + } + + virtual void animate(double monotonicTime) OVERRIDE + { + m_testHooks->animate(monotonicTime); + } + + virtual void layout() OVERRIDE + { + m_testHooks->layout(); + } + + virtual void applyScrollAndScale(const IntSize& scrollDelta, float scale) OVERRIDE + { + m_testHooks->applyScrollAndScale(scrollDelta, scale); + } + + virtual PassOwnPtr<WebCompositorOutputSurface> createOutputSurface() OVERRIDE + { + return m_testHooks->createOutputSurface(); + } + + virtual void willCommit() OVERRIDE + { + } + + virtual void didCommit() OVERRIDE + { + m_testHooks->didCommit(); + } + + virtual void didCommitAndDrawFrame() OVERRIDE + { + m_testHooks->didCommitAndDrawFrame(); + } + + virtual void didCompleteSwapBuffers() OVERRIDE + { + } + + virtual void didRecreateOutputSurface(bool succeeded) OVERRIDE + { + m_testHooks->didRecreateOutputSurface(succeeded); + } + + virtual void scheduleComposite() OVERRIDE + { + m_testHooks->scheduleComposite(); + } + +private: + explicit MockLayerTreeHostClient(TestHooks* testHooks) : m_testHooks(testHooks) { } + + TestHooks* m_testHooks; +}; + +class TimeoutTask : public WebThread::Task { +public: + explicit TimeoutTask(CCThreadedTest* test) + : m_test(test) + { + } + + void clearTest() + { + m_test = 0; + } + + virtual ~TimeoutTask() + { + if (m_test) + m_test->clearTimeout(); + } + + virtual void run() + { + if (m_test) + m_test->timeout(); + } + +private: + CCThreadedTest* m_test; +}; + +class BeginTask : public WebThread::Task { +public: + explicit BeginTask(CCThreadedTest* test) + : m_test(test) + { + } + + virtual ~BeginTask() { } + virtual void run() + { + m_test->doBeginTest(); + } +private: + CCThreadedTest* m_test; +}; + +class EndTestTask : public WebThread::Task { +public: + explicit EndTestTask(CCThreadedTest* test) + : m_test(test) + { + } + + virtual ~EndTestTask() + { + if (m_test) + m_test->clearEndTestTask(); + } + + void clearTest() + { + m_test = 0; + } + + virtual void run() + { + if (m_test) + m_test->endTest(); + } + +private: + CCThreadedTest* m_test; +}; + +CCThreadedTest::CCThreadedTest() + : m_beginning(false) + , m_endWhenBeginReturns(false) + , m_timedOut(false) + , m_finished(false) + , m_scheduled(false) + , m_started(false) + , m_endTestTask(0) +{ } + +void CCThreadedTest::endTest() +{ + m_finished = true; + + // If we are called from the CCThread, re-call endTest on the main thread. + if (!isMainThread()) + m_mainThreadProxy->postTask(createCCThreadTask(this, &CCThreadedTest::endTest)); + else { + // For the case where we endTest during beginTest(), set a flag to indicate that + // the test should end the second beginTest regains control. + if (m_beginning) + m_endWhenBeginReturns = true; + else + onEndTest(static_cast<void*>(this)); + } +} + +void CCThreadedTest::endTestAfterDelay(int delayMilliseconds) +{ + // If we are called from the CCThread, re-call endTest on the main thread. + if (!isMainThread()) + m_mainThreadProxy->postTask(createCCThreadTask(this, &CCThreadedTest::endTestAfterDelay, delayMilliseconds)); + else { + m_endTestTask = new EndTestTask(this); + WebKit::Platform::current()->currentThread()->postDelayedTask(m_endTestTask, delayMilliseconds); + } +} + +void CCThreadedTest::postSetNeedsAnimateToMainThread() +{ + callOnMainThread(CCThreadedTest::dispatchSetNeedsAnimate, this); +} + +void CCThreadedTest::postAddAnimationToMainThread() +{ + callOnMainThread(CCThreadedTest::dispatchAddAnimation, this); +} + +void CCThreadedTest::postAddInstantAnimationToMainThread() +{ + callOnMainThread(CCThreadedTest::dispatchAddInstantAnimation, this); +} + +void CCThreadedTest::postSetNeedsCommitToMainThread() +{ + callOnMainThread(CCThreadedTest::dispatchSetNeedsCommit, this); +} + +void CCThreadedTest::postAcquireLayerTextures() +{ + callOnMainThread(CCThreadedTest::dispatchAcquireLayerTextures, this); +} + +void CCThreadedTest::postSetNeedsRedrawToMainThread() +{ + callOnMainThread(CCThreadedTest::dispatchSetNeedsRedraw, this); +} + +void CCThreadedTest::postSetNeedsAnimateAndCommitToMainThread() +{ + callOnMainThread(CCThreadedTest::dispatchSetNeedsAnimateAndCommit, this); +} + +void CCThreadedTest::postSetVisibleToMainThread(bool visible) +{ + callOnMainThread(visible ? CCThreadedTest::dispatchSetVisible : CCThreadedTest::dispatchSetInvisible, this); +} + +void CCThreadedTest::postDidAddAnimationToMainThread() +{ + callOnMainThread(CCThreadedTest::dispatchDidAddAnimation, this); +} + +void CCThreadedTest::doBeginTest() +{ + ASSERT(isMainThread()); + m_client = MockLayerTreeHostClient::create(this); + + RefPtr<LayerChromium> rootLayer = LayerChromium::create(); + m_layerTreeHost = MockLayerTreeHost::create(this, m_client.get(), rootLayer, m_settings); + ASSERT_TRUE(m_layerTreeHost); + rootLayer->setLayerTreeHost(m_layerTreeHost.get()); + m_layerTreeHost->setSurfaceReady(); + + m_started = true; + m_beginning = true; + beginTest(); + m_beginning = false; + if (m_endWhenBeginReturns) + onEndTest(static_cast<void*>(this)); +} + +void CCThreadedTest::timeout() +{ + m_timedOut = true; + endTest(); +} + +void CCThreadedTest::scheduleComposite() +{ + if (!m_started || m_scheduled || m_finished) + return; + m_scheduled = true; + callOnMainThread(&CCThreadedTest::dispatchComposite, this); +} + +void CCThreadedTest::onEndTest(void* self) +{ + ASSERT(isMainThread()); + WebKit::Platform::current()->currentThread()->exitRunLoop(); +} + +void CCThreadedTest::dispatchSetNeedsAnimate(void* self) +{ + ASSERT(isMainThread()); + + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT(test); + if (test->m_finished) + return; + + if (test->m_layerTreeHost) + test->m_layerTreeHost->setNeedsAnimate(); +} + +void CCThreadedTest::dispatchAddInstantAnimation(void* self) +{ + ASSERT(isMainThread()); + + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT(test); + if (test->m_finished) + return; + + if (test->m_layerTreeHost && test->m_layerTreeHost->rootLayer()) + addOpacityTransitionToLayer(*test->m_layerTreeHost->rootLayer(), 0, 0, 0.5, false); +} + +void CCThreadedTest::dispatchAddAnimation(void* self) +{ + ASSERT(isMainThread()); + + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT(test); + if (test->m_finished) + return; + + if (test->m_layerTreeHost && test->m_layerTreeHost->rootLayer()) + addOpacityTransitionToLayer(*test->m_layerTreeHost->rootLayer(), 10, 0, 0.5, true); +} + +void CCThreadedTest::dispatchSetNeedsAnimateAndCommit(void* self) +{ + ASSERT(isMainThread()); + + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT(test); + if (test->m_finished) + return; + + if (test->m_layerTreeHost) { + test->m_layerTreeHost->setNeedsAnimate(); + test->m_layerTreeHost->setNeedsCommit(); + } +} + +void CCThreadedTest::dispatchSetNeedsCommit(void* self) +{ + ASSERT(isMainThread()); + + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT_TRUE(test); + if (test->m_finished) + return; + + if (test->m_layerTreeHost) + test->m_layerTreeHost->setNeedsCommit(); +} + +void CCThreadedTest::dispatchAcquireLayerTextures(void* self) +{ + ASSERT(isMainThread()); + + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT_TRUE(test); + if (test->m_finished) + return; + + if (test->m_layerTreeHost) + test->m_layerTreeHost->acquireLayerTextures(); +} + +void CCThreadedTest::dispatchSetNeedsRedraw(void* self) +{ + ASSERT(isMainThread()); + + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT_TRUE(test); + if (test->m_finished) + return; + + if (test->m_layerTreeHost) + test->m_layerTreeHost->setNeedsRedraw(); +} + +void CCThreadedTest::dispatchSetVisible(void* self) +{ + ASSERT(isMainThread()); + + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT(test); + if (test->m_finished) + return; + + if (test->m_layerTreeHost) + test->m_layerTreeHost->setVisible(true); +} + +void CCThreadedTest::dispatchSetInvisible(void* self) +{ + ASSERT(isMainThread()); + + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT(test); + if (test->m_finished) + return; + + if (test->m_layerTreeHost) + test->m_layerTreeHost->setVisible(false); +} + +void CCThreadedTest::dispatchComposite(void* self) +{ + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT(isMainThread()); + ASSERT(test); + test->m_scheduled = false; + if (test->m_layerTreeHost && !test->m_finished) + test->m_layerTreeHost->composite(); +} + +void CCThreadedTest::dispatchDidAddAnimation(void* self) +{ + ASSERT(isMainThread()); + + CCThreadedTest* test = static_cast<CCThreadedTest*>(self); + ASSERT(test); + if (test->m_finished) + return; + + if (test->m_layerTreeHost) + test->m_layerTreeHost->didAddAnimation(); +} + +void CCThreadedTest::runTest(bool threaded) +{ + // For these tests, we will enable threaded animations. + WebCompositor::setAcceleratedAnimationEnabled(true); + + if (threaded) { + m_webThread = adoptPtr(WebKit::Platform::current()->createThread("CCThreadedTest")); + WebCompositor::initialize(m_webThread.get()); + } else + WebCompositor::initialize(0); + + ASSERT(CCProxy::isMainThread()); + m_mainThreadProxy = CCScopedThreadProxy::create(CCProxy::mainThread()); + + m_beginTask = new BeginTask(this); + WebKit::Platform::current()->currentThread()->postDelayedTask(m_beginTask, 0); // postDelayedTask takes ownership of the task + m_timeoutTask = new TimeoutTask(this); + WebKit::Platform::current()->currentThread()->postDelayedTask(m_timeoutTask, 5000); + WebKit::Platform::current()->currentThread()->enterRunLoop(); + + if (m_layerTreeHost && m_layerTreeHost->rootLayer()) + m_layerTreeHost->rootLayer()->setLayerTreeHost(0); + m_layerTreeHost.clear(); + + if (m_timeoutTask) + m_timeoutTask->clearTest(); + + if (m_endTestTask) + m_endTestTask->clearTest(); + + ASSERT_FALSE(m_layerTreeHost.get()); + m_client.clear(); + if (m_timedOut) { + FAIL() << "Test timed out"; + WebCompositor::shutdown(); + return; + } + afterTest(); + WebCompositor::shutdown(); +} + +} // namespace WebKitTests diff --git a/cc/CCThreadedTest.h b/cc/CCThreadedTest.h new file mode 100644 index 0000000..6971c42 --- /dev/null +++ b/cc/CCThreadedTest.h @@ -0,0 +1,207 @@ +// 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. + +#ifndef CCThreadedTest_h +#define CCThreadedTest_h + +#include "CCLayerTreeHost.h" +#include "CCLayerTreeHostImpl.h" +#include "CCScopedThreadProxy.h" +#include "CompositorFakeWebGraphicsContext3D.h" +#include <gtest/gtest.h> +#include <public/WebAnimationDelegate.h> + +namespace WebCore { +class CCLayerImpl; +class CCLayerTreeHost; +class CCLayerTreeHostClient; +class CCLayerTreeHostImpl; +class GraphicsContext3D; +} + +namespace WebKit { +class WebThread; +} + +namespace WebKitTests { + +// Used by test stubs to notify the test when something interesting happens. +class TestHooks : public WebKit::WebAnimationDelegate { +public: + virtual void beginCommitOnCCThread(WebCore::CCLayerTreeHostImpl*) { } + virtual void commitCompleteOnCCThread(WebCore::CCLayerTreeHostImpl*) { } + virtual bool prepareToDrawOnCCThread(WebCore::CCLayerTreeHostImpl*) { return true; } + virtual void drawLayersOnCCThread(WebCore::CCLayerTreeHostImpl*) { } + virtual void animateLayers(WebCore::CCLayerTreeHostImpl*, double monotonicTime) { } + virtual void willAnimateLayers(WebCore::CCLayerTreeHostImpl*, double monotonicTime) { } + virtual void applyScrollAndScale(const WebCore::IntSize&, float) { } + virtual void animate(double monotonicTime) { } + virtual void layout() { } + virtual void didRecreateOutputSurface(bool succeeded) { } + virtual void didAddAnimation() { } + virtual void didCommit() { } + virtual void didCommitAndDrawFrame() { } + virtual void scheduleComposite() { } + + // Implementation of WebAnimationDelegate + virtual void notifyAnimationStarted(double time) OVERRIDE { } + virtual void notifyAnimationFinished(double time) OVERRIDE { } + + virtual PassOwnPtr<WebKit::WebCompositorOutputSurface> createOutputSurface(); +}; + +class TimeoutTask; +class BeginTask; +class EndTestTask; + +class MockCCLayerTreeHostClient : public WebCore::CCLayerTreeHostClient { +}; + +// The CCThreadedTests runs with the main loop running. It instantiates a single MockLayerTreeHost and associated +// MockLayerTreeHostImpl/MockLayerTreeHostClient. +// +// beginTest() is called once the main message loop is running and the layer tree host is initialized. +// +// Key stages of the drawing loop, e.g. drawing or commiting, redirect to CCThreadedTest methods of similar names. +// To track the commit process, override these functions. +// +// The test continues until someone calls endTest. endTest can be called on any thread, but be aware that +// ending the test is an asynchronous process. +class CCThreadedTest : public testing::Test, public TestHooks { +public: + virtual void afterTest() = 0; + virtual void beginTest() = 0; + + void endTest(); + void endTestAfterDelay(int delayMilliseconds); + + void postSetNeedsAnimateToMainThread(); + void postAddAnimationToMainThread(); + void postAddInstantAnimationToMainThread(); + void postSetNeedsCommitToMainThread(); + void postAcquireLayerTextures(); + void postSetNeedsRedrawToMainThread(); + void postSetNeedsAnimateAndCommitToMainThread(); + void postSetVisibleToMainThread(bool visible); + void postDidAddAnimationToMainThread(); + + void doBeginTest(); + void timeout(); + + void clearTimeout() { m_timeoutTask = 0; } + void clearEndTestTask() { m_endTestTask = 0; } + + WebCore::CCLayerTreeHost* layerTreeHost() { return m_layerTreeHost.get(); } + +protected: + CCThreadedTest(); + + virtual void scheduleComposite(); + + static void onEndTest(void* self); + + static void dispatchSetNeedsAnimate(void* self); + static void dispatchAddInstantAnimation(void* self); + static void dispatchAddAnimation(void* self); + static void dispatchSetNeedsAnimateAndCommit(void* self); + static void dispatchSetNeedsCommit(void* self); + static void dispatchAcquireLayerTextures(void* self); + static void dispatchSetNeedsRedraw(void* self); + static void dispatchSetVisible(void* self); + static void dispatchSetInvisible(void* self); + static void dispatchComposite(void* self); + static void dispatchDidAddAnimation(void* self); + + virtual void runTest(bool threaded); + WebKit::WebThread* webThread() const { return m_webThread.get(); } + + WebCore::CCLayerTreeSettings m_settings; + OwnPtr<MockCCLayerTreeHostClient> m_client; + OwnPtr<WebCore::CCLayerTreeHost> m_layerTreeHost; + +private: + bool m_beginning; + bool m_endWhenBeginReturns; + bool m_timedOut; + bool m_finished; + bool m_scheduled; + bool m_started; + + OwnPtr<WebKit::WebThread> m_webThread; + RefPtr<WebCore::CCScopedThreadProxy> m_mainThreadProxy; + TimeoutTask* m_timeoutTask; + BeginTask* m_beginTask; + EndTestTask* m_endTestTask; +}; + +class CCThreadedTestThreadOnly : public CCThreadedTest { +public: + void runTestThreaded() + { + CCThreadedTest::runTest(true); + } +}; + +// Adapts CCLayerTreeHostImpl for test. Runs real code, then invokes test hooks. +class MockLayerTreeHostImpl : public WebCore::CCLayerTreeHostImpl { +public: + static PassOwnPtr<MockLayerTreeHostImpl> create(TestHooks*, const WebCore::CCLayerTreeSettings&, WebCore::CCLayerTreeHostImplClient*); + + virtual void beginCommit(); + virtual void commitComplete(); + virtual bool prepareToDraw(FrameData&); + virtual void drawLayers(const FrameData&); + + // Make these public. + typedef Vector<WebCore::CCLayerImpl*> CCLayerList; + using CCLayerTreeHostImpl::calculateRenderSurfaceLayerList; + +protected: + virtual void animateLayers(double monotonicTime, double wallClockTime); + virtual double lowFrequencyAnimationInterval() const; + +private: + MockLayerTreeHostImpl(TestHooks*, const WebCore::CCLayerTreeSettings&, WebCore::CCLayerTreeHostImplClient*); + + TestHooks* m_testHooks; +}; + +class CompositorFakeWebGraphicsContext3DWithTextureTracking : public WebKit::CompositorFakeWebGraphicsContext3D { +public: + static PassOwnPtr<CompositorFakeWebGraphicsContext3DWithTextureTracking> create(Attributes); + + virtual WebKit::WebGLId createTexture(); + + virtual void deleteTexture(WebKit::WebGLId texture); + + virtual void bindTexture(WebKit::WGC3Denum target, WebKit::WebGLId texture); + + int numTextures() const; + int texture(int texture) const; + void resetTextures(); + + int numUsedTextures() const; + bool usedTexture(int texture) const; + void resetUsedTextures(); + +private: + explicit CompositorFakeWebGraphicsContext3DWithTextureTracking(Attributes attrs); + + Vector<WebKit::WebGLId> m_textures; + HashSet<WebKit::WebGLId, DefaultHash<WebKit::WebGLId>::Hash, WTF::UnsignedWithZeroKeyHashTraits<WebKit::WebGLId> > m_usedTextures; +}; + +} // namespace WebKitTests + +#define SINGLE_AND_MULTI_THREAD_TEST_F(TEST_FIXTURE_NAME) \ + TEST_F(TEST_FIXTURE_NAME, runSingleThread) \ + { \ + runTest(false); \ + } \ + TEST_F(TEST_FIXTURE_NAME, runMultiThread) \ + { \ + runTest(true); \ + } + +#endif // CCThreadedTest_h diff --git a/cc/CCTileDrawQuad.cpp b/cc/CCTileDrawQuad.cpp new file mode 100644 index 0000000..6d13788 --- /dev/null +++ b/cc/CCTileDrawQuad.cpp @@ -0,0 +1,39 @@ +// Copyright 2012 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 "CCTileDrawQuad.h" + +namespace WebCore { + +PassOwnPtr<CCTileDrawQuad> CCTileDrawQuad::create(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, const IntRect& opaqueRect, unsigned resourceId, const IntPoint& textureOffset, const IntSize& textureSize, GC3Dint textureFilter, bool swizzleContents, bool leftEdgeAA, bool topEdgeAA, bool rightEdgeAA, bool bottomEdgeAA) +{ + return adoptPtr(new CCTileDrawQuad(sharedQuadState, quadRect, opaqueRect, resourceId, textureOffset, textureSize, textureFilter, swizzleContents, leftEdgeAA, topEdgeAA, rightEdgeAA, bottomEdgeAA)); +} + +CCTileDrawQuad::CCTileDrawQuad(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, const IntRect& opaqueRect, unsigned resourceId, const IntPoint& textureOffset, const IntSize& textureSize, GC3Dint textureFilter, bool swizzleContents, bool leftEdgeAA, bool topEdgeAA, bool rightEdgeAA, bool bottomEdgeAA) + : CCDrawQuad(sharedQuadState, CCDrawQuad::TiledContent, quadRect) + , m_resourceId(resourceId) + , m_textureOffset(textureOffset) + , m_textureSize(textureSize) + , m_textureFilter(textureFilter) + , m_swizzleContents(swizzleContents) + , m_leftEdgeAA(leftEdgeAA) + , m_topEdgeAA(topEdgeAA) + , m_rightEdgeAA(rightEdgeAA) + , m_bottomEdgeAA(bottomEdgeAA) +{ + if (isAntialiased()) + m_needsBlending = true; + m_opaqueRect = opaqueRect; +} + +const CCTileDrawQuad* CCTileDrawQuad::materialCast(const CCDrawQuad* quad) +{ + ASSERT(quad->material() == CCDrawQuad::TiledContent); + return static_cast<const CCTileDrawQuad*>(quad); +} + +} diff --git a/cc/CCTileDrawQuad.h b/cc/CCTileDrawQuad.h new file mode 100644 index 0000000..9a9969c --- /dev/null +++ b/cc/CCTileDrawQuad.h @@ -0,0 +1,54 @@ +// Copyright 2012 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. + +#ifndef CCTileDrawQuad_h +#define CCTileDrawQuad_h + +#include "CCDrawQuad.h" +#include "GraphicsTypes3D.h" +#include "IntPoint.h" +#include "IntSize.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +#pragma pack(push, 4) + +class CCTileDrawQuad : public CCDrawQuad { +public: + static PassOwnPtr<CCTileDrawQuad> create(const CCSharedQuadState*, const IntRect& quadRect, const IntRect& opaqueRect, unsigned resourceId, const IntPoint& textureOffset, const IntSize& textureSize, GC3Dint textureFilter, bool swizzleContents, bool leftEdgeAA, bool topEdgeAA, bool rightEdgeAA, bool bottomEdgeAA); + + unsigned resourceId() const { return m_resourceId; } + IntPoint textureOffset() const { return m_textureOffset; } + IntSize textureSize() const { return m_textureSize; } + GC3Dint textureFilter() const { return m_textureFilter; } + bool swizzleContents() const { return m_swizzleContents; } + + bool leftEdgeAA() const { return m_leftEdgeAA; } + bool topEdgeAA() const { return m_topEdgeAA; } + bool rightEdgeAA() const { return m_rightEdgeAA; } + bool bottomEdgeAA() const { return m_bottomEdgeAA; } + + bool isAntialiased() const { return leftEdgeAA() || topEdgeAA() || rightEdgeAA() || bottomEdgeAA(); } + + static const CCTileDrawQuad* materialCast(const CCDrawQuad*); +private: + CCTileDrawQuad(const CCSharedQuadState*, const IntRect& quadRect, const IntRect& opaqueRect, unsigned resourceId, const IntPoint& textureOffset, const IntSize& textureSize, GC3Dint textureFilter, bool swizzleContents, bool leftEdgeAA, bool topEdgeAA, bool rightEdgeAA, bool bottomEdgeAA); + + unsigned m_resourceId; + IntPoint m_textureOffset; + IntSize m_textureSize; + GC3Dint m_textureFilter; + bool m_swizzleContents; + bool m_leftEdgeAA; + bool m_topEdgeAA; + bool m_rightEdgeAA; + bool m_bottomEdgeAA; +}; + +#pragma pack(pop) + +} + +#endif diff --git a/cc/CCTiledLayerImpl.cpp b/cc/CCTiledLayerImpl.cpp new file mode 100644 index 0000000..a5d3cea --- /dev/null +++ b/cc/CCTiledLayerImpl.cpp @@ -0,0 +1,219 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCTiledLayerImpl.h" + +#include "CCCheckerboardDrawQuad.h" +#include "CCDebugBorderDrawQuad.h" +#include "CCLayerTilingData.h" +#include "CCMathUtil.h" +#include "CCQuadSink.h" +#include "CCSolidColorDrawQuad.h" +#include "CCTileDrawQuad.h" +#include "FloatQuad.h" +#include "GraphicsContext3D.h" +#include "SkColor.h" +#include "TextStream.h" +#include <wtf/text/WTFString.h> + +using namespace std; +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +static const int debugTileBorderWidth = 1; +static const int debugTileBorderAlpha = 100; +static const int debugTileBorderColorRed = 80; +static const int debugTileBorderColorGreen = 200; +static const int debugTileBorderColorBlue = 200; +static const int debugTileBorderMissingTileColorRed = 255; +static const int debugTileBorderMissingTileColorGreen = 0; +static const int debugTileBorderMissingTileColorBlue = 0; + +class DrawableTile : public CCLayerTilingData::Tile { + WTF_MAKE_NONCOPYABLE(DrawableTile); +public: + static PassOwnPtr<DrawableTile> create() { return adoptPtr(new DrawableTile()); } + + CCResourceProvider::ResourceId resourceId() const { return m_resourceId; } + void setResourceId(CCResourceProvider::ResourceId resourceId) { m_resourceId = resourceId; } + +private: + DrawableTile() : m_resourceId(0) { } + + CCResourceProvider::ResourceId m_resourceId; +}; + +CCTiledLayerImpl::CCTiledLayerImpl(int id) + : CCLayerImpl(id) + , m_skipsDraw(true) + , m_contentsSwizzled(false) +{ +} + +CCTiledLayerImpl::~CCTiledLayerImpl() +{ +} + +CCResourceProvider::ResourceId CCTiledLayerImpl::contentsResourceId() const +{ + // This function is only valid for single texture layers, e.g. masks. + ASSERT(m_tiler); + ASSERT(m_tiler->numTilesX() == 1); + ASSERT(m_tiler->numTilesY() == 1); + + DrawableTile* tile = tileAt(0, 0); + CCResourceProvider::ResourceId resourceId = tile ? tile->resourceId() : 0; + ASSERT(resourceId); + + return resourceId; +} + +void CCTiledLayerImpl::dumpLayerProperties(TextStream& ts, int indent) const +{ + CCLayerImpl::dumpLayerProperties(ts, indent); + writeIndent(ts, indent); + ts << "skipsDraw: " << (!m_tiler || m_skipsDraw) << "\n"; +} + +bool CCTiledLayerImpl::hasTileAt(int i, int j) const +{ + return m_tiler->tileAt(i, j); +} + +bool CCTiledLayerImpl::hasTextureIdForTileAt(int i, int j) const +{ + return hasTileAt(i, j) && tileAt(i, j)->resourceId(); +} + +DrawableTile* CCTiledLayerImpl::tileAt(int i, int j) const +{ + return static_cast<DrawableTile*>(m_tiler->tileAt(i, j)); +} + +DrawableTile* CCTiledLayerImpl::createTile(int i, int j) +{ + OwnPtr<DrawableTile> tile(DrawableTile::create()); + DrawableTile* addedTile = tile.get(); + m_tiler->addTile(tile.release(), i, j); + return addedTile; +} + +void CCTiledLayerImpl::appendQuads(CCQuadSink& quadSink, bool& hadMissingTiles) +{ + const IntRect& contentRect = visibleContentRect(); + + if (!m_tiler || m_tiler->hasEmptyBounds() || contentRect.isEmpty()) + return; + + CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState()); + appendDebugBorderQuad(quadSink, sharedQuadState); + + int left, top, right, bottom; + m_tiler->contentRectToTileIndices(contentRect, left, top, right, bottom); + + if (hasDebugBorders()) { + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + DrawableTile* tile = tileAt(i, j); + IntRect tileRect = m_tiler->tileBounds(i, j); + SkColor borderColor; + + if (m_skipsDraw || !tile || !tile->resourceId()) + borderColor = SkColorSetARGB(debugTileBorderAlpha, debugTileBorderMissingTileColorRed, debugTileBorderMissingTileColorGreen, debugTileBorderMissingTileColorBlue); + else + borderColor = SkColorSetARGB(debugTileBorderAlpha, debugTileBorderColorRed, debugTileBorderColorGreen, debugTileBorderColorBlue); + quadSink.append(CCDebugBorderDrawQuad::create(sharedQuadState, tileRect, borderColor, debugTileBorderWidth)); + } + } + } + + if (m_skipsDraw) + return; + + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + DrawableTile* tile = tileAt(i, j); + IntRect tileRect = m_tiler->tileBounds(i, j); + IntRect displayRect = tileRect; + tileRect.intersect(contentRect); + + // Skip empty tiles. + if (tileRect.isEmpty()) + continue; + + if (!tile || !tile->resourceId()) { + if (drawCheckerboardForMissingTiles()) + hadMissingTiles |= quadSink.append(CCCheckerboardDrawQuad::create(sharedQuadState, tileRect)); + else + hadMissingTiles |= quadSink.append(CCSolidColorDrawQuad::create(sharedQuadState, tileRect, backgroundColor())); + continue; + } + + IntRect tileOpaqueRect = tile->opaqueRect(); + tileOpaqueRect.intersect(contentRect); + + // Keep track of how the top left has moved, so the texture can be + // offset the same amount. + IntSize displayOffset = tileRect.minXMinYCorner() - displayRect.minXMinYCorner(); + IntPoint textureOffset = m_tiler->textureOffset(i, j) + displayOffset; + float tileWidth = static_cast<float>(m_tiler->tileSize().width()); + float tileHeight = static_cast<float>(m_tiler->tileSize().height()); + IntSize textureSize(tileWidth, tileHeight); + + bool clipped = false; + FloatQuad visibleContentInTargetQuad = CCMathUtil::mapQuad(drawTransform(), FloatQuad(visibleContentRect()), clipped); + bool isAxisAlignedInTarget = !clipped && visibleContentInTargetQuad.isRectilinear(); + bool useAA = m_tiler->hasBorderTexels() && !isAxisAlignedInTarget; + + bool leftEdgeAA = !i && useAA; + bool topEdgeAA = !j && useAA; + bool rightEdgeAA = i == m_tiler->numTilesX() - 1 && useAA; + bool bottomEdgeAA = j == m_tiler->numTilesY() - 1 && useAA; + + const GC3Dint textureFilter = m_tiler->hasBorderTexels() ? GraphicsContext3D::LINEAR : GraphicsContext3D::NEAREST; + quadSink.append(CCTileDrawQuad::create(sharedQuadState, tileRect, tileOpaqueRect, tile->resourceId(), textureOffset, textureSize, textureFilter, contentsSwizzled(), leftEdgeAA, topEdgeAA, rightEdgeAA, bottomEdgeAA)); + } + } +} + +void CCTiledLayerImpl::setTilingData(const CCLayerTilingData& tiler) +{ + if (m_tiler) + m_tiler->reset(); + else + m_tiler = CCLayerTilingData::create(tiler.tileSize(), tiler.hasBorderTexels() ? CCLayerTilingData::HasBorderTexels : CCLayerTilingData::NoBorderTexels); + *m_tiler = tiler; +} + +void CCTiledLayerImpl::pushTileProperties(int i, int j, CCResourceProvider::ResourceId resourceId, const IntRect& opaqueRect) +{ + DrawableTile* tile = tileAt(i, j); + if (!tile) + tile = createTile(i, j); + tile->setResourceId(resourceId); + tile->setOpaqueRect(opaqueRect); +} + +Region CCTiledLayerImpl::visibleContentOpaqueRegion() const +{ + if (m_skipsDraw) + return Region(); + if (opaque()) + return visibleContentRect(); + return m_tiler->opaqueRegionInContentRect(visibleContentRect()); +} + +void CCTiledLayerImpl::didLoseContext() +{ + m_tiler->reset(); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCTiledLayerImpl.h b/cc/CCTiledLayerImpl.h new file mode 100644 index 0000000..8ddc54d --- /dev/null +++ b/cc/CCTiledLayerImpl.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef CCTiledLayerImpl_h +#define CCTiledLayerImpl_h + +#include "CCLayerImpl.h" +#include <public/WebTransformationMatrix.h> + +namespace WebCore { + +class CCLayerTilingData; +class DrawableTile; + +class CCTiledLayerImpl : public CCLayerImpl { +public: + static PassOwnPtr<CCTiledLayerImpl> create(int id) + { + return adoptPtr(new CCTiledLayerImpl(id)); + } + virtual ~CCTiledLayerImpl(); + + virtual void appendQuads(CCQuadSink&, bool& hadMissingTiles) OVERRIDE; + + virtual CCResourceProvider::ResourceId contentsResourceId() const OVERRIDE; + + virtual void dumpLayerProperties(TextStream&, int indent) const OVERRIDE; + + void setSkipsDraw(bool skipsDraw) { m_skipsDraw = skipsDraw; } + void setTilingData(const CCLayerTilingData& tiler); + void pushTileProperties(int, int, CCResourceProvider::ResourceId, const IntRect& opaqueRect); + + void setContentsSwizzled(bool contentsSwizzled) { m_contentsSwizzled = contentsSwizzled; } + bool contentsSwizzled() const { return m_contentsSwizzled; } + + virtual Region visibleContentOpaqueRegion() const OVERRIDE; + virtual void didLoseContext() OVERRIDE; + +protected: + explicit CCTiledLayerImpl(int id); + // Exposed for testing. + bool hasTileAt(int, int) const; + bool hasTextureIdForTileAt(int, int) const; + +private: + + virtual const char* layerTypeAsString() const OVERRIDE { return "ContentLayer"; } + + DrawableTile* tileAt(int, int) const; + DrawableTile* createTile(int, int); + + bool m_skipsDraw; + bool m_contentsSwizzled; + + OwnPtr<CCLayerTilingData> m_tiler; +}; + +} + +#endif // CCTiledLayerImpl_h diff --git a/cc/CCTiledLayerImplTest.cpp b/cc/CCTiledLayerImplTest.cpp new file mode 100644 index 0000000..d67907a --- /dev/null +++ b/cc/CCTiledLayerImplTest.cpp @@ -0,0 +1,243 @@ +// Copyright 2012 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 "CCTiledLayerImpl.h" + +#include "CCLayerTestCommon.h" +#include "CCLayerTilingData.h" +#include "CCSingleThreadProxy.h" +#include "CCTileDrawQuad.h" +#include "MockCCQuadCuller.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using namespace WebCore; +using namespace CCLayerTestCommon; + +namespace { + +// Create a default tiled layer with textures for all tiles and a default +// visibility of the entire layer size. +static PassOwnPtr<CCTiledLayerImpl> createLayer(const IntSize& tileSize, const IntSize& layerSize, CCLayerTilingData::BorderTexelOption borderTexels) +{ + OwnPtr<CCTiledLayerImpl> layer = CCTiledLayerImpl::create(1); + OwnPtr<CCLayerTilingData> tiler = CCLayerTilingData::create(tileSize, borderTexels); + tiler->setBounds(layerSize); + layer->setTilingData(*tiler); + layer->setSkipsDraw(false); + layer->setVisibleContentRect(IntRect(IntPoint(), layerSize)); + layer->setDrawOpacity(1); + layer->setBounds(layerSize); + layer->setContentBounds(layerSize); + layer->createRenderSurface(); + layer->setRenderTarget(layer.get()); + + CCResourceProvider::ResourceId resourceId = 1; + for (int i = 0; i < tiler->numTilesX(); ++i) + for (int j = 0; j < tiler->numTilesY(); ++j) + layer->pushTileProperties(i, j, resourceId++, IntRect(0, 0, 1, 1)); + + return layer.release(); +} + +TEST(CCTiledLayerImplTest, emptyQuadList) +{ + DebugScopedSetImplThread scopedImplThread; + + const IntSize tileSize(90, 90); + const int numTilesX = 8; + const int numTilesY = 4; + const IntSize layerSize(tileSize.width() * numTilesX, tileSize.height() * numTilesY); + + // Verify default layer does creates quads + { + OwnPtr<CCTiledLayerImpl> layer = createLayer(tileSize, layerSize, CCLayerTilingData::NoBorderTexels); + MockCCQuadCuller quadCuller; + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); + const unsigned numTiles = numTilesX * numTilesY; + EXPECT_EQ(quadCuller.quadList().size(), numTiles); + } + + // Layer with empty visible layer rect produces no quads + { + OwnPtr<CCTiledLayerImpl> layer = createLayer(tileSize, layerSize, CCLayerTilingData::NoBorderTexels); + layer->setVisibleContentRect(IntRect()); + + MockCCQuadCuller quadCuller; + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); + EXPECT_EQ(quadCuller.quadList().size(), 0u); + } + + // Layer with non-intersecting visible layer rect produces no quads + { + OwnPtr<CCTiledLayerImpl> layer = createLayer(tileSize, layerSize, CCLayerTilingData::NoBorderTexels); + + IntRect outsideBounds(IntPoint(-100, -100), IntSize(50, 50)); + layer->setVisibleContentRect(outsideBounds); + + MockCCQuadCuller quadCuller; + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); + EXPECT_EQ(quadCuller.quadList().size(), 0u); + } + + // Layer with skips draw produces no quads + { + OwnPtr<CCTiledLayerImpl> layer = createLayer(tileSize, layerSize, CCLayerTilingData::NoBorderTexels); + layer->setSkipsDraw(true); + + MockCCQuadCuller quadCuller; + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); + EXPECT_EQ(quadCuller.quadList().size(), 0u); + } +} + +TEST(CCTiledLayerImplTest, checkerboarding) +{ + DebugScopedSetImplThread scopedImplThread; + + const IntSize tileSize(10, 10); + const int numTilesX = 2; + const int numTilesY = 2; + const IntSize layerSize(tileSize.width() * numTilesX, tileSize.height() * numTilesY); + + OwnPtr<CCTiledLayerImpl> layer = createLayer(tileSize, layerSize, CCLayerTilingData::NoBorderTexels); + + // No checkerboarding + { + MockCCQuadCuller quadCuller; + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); + EXPECT_EQ(quadCuller.quadList().size(), 4u); + EXPECT_FALSE(hadMissingTiles); + + for (size_t i = 0; i < quadCuller.quadList().size(); ++i) + EXPECT_EQ(quadCuller.quadList()[i]->material(), CCDrawQuad::TiledContent); + } + + for (int i = 0; i < numTilesX; ++i) + for (int j = 0; j < numTilesY; ++j) + layer->pushTileProperties(i, j, 0, IntRect()); + + // All checkerboarding + { + MockCCQuadCuller quadCuller; + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); + EXPECT_TRUE(hadMissingTiles); + EXPECT_EQ(quadCuller.quadList().size(), 4u); + for (size_t i = 0; i < quadCuller.quadList().size(); ++i) + EXPECT_NE(quadCuller.quadList()[i]->material(), CCDrawQuad::TiledContent); + } +} + +static void getQuads(CCQuadList& quads, CCSharedQuadStateList& sharedStates, IntSize tileSize, const IntSize& layerSize, CCLayerTilingData::BorderTexelOption borderTexelOption, const IntRect& visibleContentRect) +{ + OwnPtr<CCTiledLayerImpl> layer = createLayer(tileSize, layerSize, borderTexelOption); + layer->setVisibleContentRect(visibleContentRect); + layer->setBounds(layerSize); + + MockCCQuadCuller quadCuller(quads, sharedStates); + bool hadMissingTiles = false; + layer->appendQuads(quadCuller, hadMissingTiles); +} + +// Test with both border texels and without. +#define WITH_AND_WITHOUT_BORDER_TEST(testFixtureName) \ + TEST(CCTiledLayerImplTest, testFixtureName##NoBorders) \ + { \ + testFixtureName(CCLayerTilingData::NoBorderTexels); \ + } \ + TEST(CCTiledLayerImplTest, testFixtureName##HasBorders) \ + { \ + testFixtureName(CCLayerTilingData::HasBorderTexels);\ + } + +static void coverageVisibleRectOnTileBoundaries(CCLayerTilingData::BorderTexelOption borders) +{ + DebugScopedSetImplThread scopedImplThread; + + IntSize layerSize(1000, 1000); + CCQuadList quads; + CCSharedQuadStateList sharedStates; + getQuads(quads, sharedStates, IntSize(100, 100), layerSize, borders, IntRect(IntPoint(), layerSize)); + verifyQuadsExactlyCoverRect(quads, IntRect(IntPoint(), layerSize)); +} +WITH_AND_WITHOUT_BORDER_TEST(coverageVisibleRectOnTileBoundaries); + +static void coverageVisibleRectIntersectsTiles(CCLayerTilingData::BorderTexelOption borders) +{ + DebugScopedSetImplThread scopedImplThread; + + // This rect intersects the middle 3x3 of the 5x5 tiles. + IntPoint topLeft(65, 73); + IntPoint bottomRight(182, 198); + IntRect visibleContentRect(topLeft, bottomRight - topLeft); + + IntSize layerSize(250, 250); + CCQuadList quads; + CCSharedQuadStateList sharedStates; + getQuads(quads, sharedStates, IntSize(50, 50), IntSize(250, 250), CCLayerTilingData::NoBorderTexels, visibleContentRect); + verifyQuadsExactlyCoverRect(quads, visibleContentRect); +} +WITH_AND_WITHOUT_BORDER_TEST(coverageVisibleRectIntersectsTiles); + +static void coverageVisibleRectIntersectsBounds(CCLayerTilingData::BorderTexelOption borders) +{ + DebugScopedSetImplThread scopedImplThread; + + IntSize layerSize(220, 210); + IntRect visibleContentRect(IntPoint(), layerSize); + CCQuadList quads; + CCSharedQuadStateList sharedStates; + getQuads(quads, sharedStates, IntSize(100, 100), layerSize, CCLayerTilingData::NoBorderTexels, visibleContentRect); + verifyQuadsExactlyCoverRect(quads, visibleContentRect); +} +WITH_AND_WITHOUT_BORDER_TEST(coverageVisibleRectIntersectsBounds); + +TEST(CCTiledLayerImplTest, textureInfoForLayerNoBorders) +{ + DebugScopedSetImplThread scopedImplThread; + + IntSize tileSize(50, 50); + IntSize layerSize(250, 250); + CCQuadList quads; + CCSharedQuadStateList sharedStates; + getQuads(quads, sharedStates, tileSize, layerSize, CCLayerTilingData::NoBorderTexels, IntRect(IntPoint(), layerSize)); + + for (size_t i = 0; i < quads.size(); ++i) { + ASSERT_EQ(quads[i]->material(), CCDrawQuad::TiledContent) << quadString << i; + CCTileDrawQuad* quad = static_cast<CCTileDrawQuad*>(quads[i].get()); + + EXPECT_NE(quad->resourceId(), 0u) << quadString << i; + EXPECT_EQ(quad->textureOffset(), IntPoint()) << quadString << i; + EXPECT_EQ(quad->textureSize(), tileSize) << quadString << i; + EXPECT_EQ(IntRect(0, 0, 1, 1), quad->opaqueRect()) << quadString << i; + } +} + +TEST(CCTiledLayerImplTest, tileOpaqueRectForLayerNoBorders) +{ + DebugScopedSetImplThread scopedImplThread; + + IntSize tileSize(50, 50); + IntSize layerSize(250, 250); + CCQuadList quads; + CCSharedQuadStateList sharedStates; + getQuads(quads, sharedStates, tileSize, layerSize, CCLayerTilingData::NoBorderTexels, IntRect(IntPoint(), layerSize)); + + for (size_t i = 0; i < quads.size(); ++i) { + ASSERT_EQ(quads[i]->material(), CCDrawQuad::TiledContent) << quadString << i; + CCTileDrawQuad* quad = static_cast<CCTileDrawQuad*>(quads[i].get()); + + EXPECT_EQ(IntRect(0, 0, 1, 1), quad->opaqueRect()) << quadString << i; + } +} + +} // namespace diff --git a/cc/CCTimeSource.h b/cc/CCTimeSource.h new file mode 100644 index 0000000..e74c508 --- /dev/null +++ b/cc/CCTimeSource.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef CCTimeSource_h +#define CCTimeSource_h + +#include <wtf/RefCounted.h> + +namespace WebCore { + +class CCThread; + +class CCTimeSourceClient { +public: + virtual void onTimerTick() = 0; + +protected: + virtual ~CCTimeSourceClient() { } +}; + +// An generic interface for getting a reliably-ticking timesource of +// a specified rate. +// +// Be sure to call setActive(false) before releasing your reference to the +// timer, or it will keep on ticking! +class CCTimeSource : public RefCounted<CCTimeSource> { +public: + virtual ~CCTimeSource() { } + virtual void setClient(CCTimeSourceClient*) = 0; + virtual void setActive(bool) = 0; + virtual bool active() const = 0; + virtual void setTimebaseAndInterval(double timebase, double intervalSeconds) = 0; + virtual double lastTickTime() = 0; + virtual double nextTickTimeIfActivated() = 0; +}; + +} +#endif // CCSmoothedTimer_h diff --git a/cc/CCTimer.cpp b/cc/CCTimer.cpp new file mode 100644 index 0000000..52222b7 --- /dev/null +++ b/cc/CCTimer.cpp @@ -0,0 +1,79 @@ +// 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 "config.h" + +#include "CCTimer.h" + +#include "CCThread.h" + +namespace WebCore { + +class CCTimerTask : public CCThread::Task { +public: + explicit CCTimerTask(CCTimer* timer) + : CCThread::Task(0) + , m_timer(timer) + { + } + + ~CCTimerTask() + { + if (!m_timer) + return; + + ASSERT(m_timer->m_task == this); + m_timer->stop(); + } + + void performTask() + { + if (!m_timer) + return; + + CCTimerClient* client = m_timer->m_client; + + m_timer->stop(); + if (client) + client->onTimerFired(); + } + +private: + friend class CCTimer; + + CCTimer* m_timer; // null if cancelled +}; + +CCTimer::CCTimer(CCThread* thread, CCTimerClient* client) + : m_client(client) + , m_thread(thread) + , m_task(0) +{ +} + +CCTimer::~CCTimer() +{ + stop(); +} + +void CCTimer::startOneShot(double intervalSeconds) +{ + stop(); + + m_task = new CCTimerTask(this); + + // The thread expects delays in milliseconds. + m_thread->postDelayedTask(adoptPtr(m_task), intervalSeconds * 1000.0); +} + +void CCTimer::stop() +{ + if (!m_task) + return; + + m_task->m_timer = 0; + m_task = 0; +} + +} // namespace WebCore diff --git a/cc/CCTimer.h b/cc/CCTimer.h new file mode 100644 index 0000000..7cf3340 --- /dev/null +++ b/cc/CCTimer.h @@ -0,0 +1,42 @@ +// 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. + +#ifndef CCTimer_h +#define CCTimer_h + + +namespace WebCore { + +class CCThread; +class CCTimerTask; + +class CCTimerClient { +public: + virtual ~CCTimerClient() { } + + virtual void onTimerFired() = 0; +}; + +class CCTimer { +public: + CCTimer(CCThread*, CCTimerClient*); + ~CCTimer(); + + // If a previous task is pending, it will be replaced with the new one. + void startOneShot(double intervalSeconds); + void stop(); + + bool isActive() const { return m_task; } + +private: + friend class CCTimerTask; + + CCTimerClient* m_client; + CCThread* m_thread; + CCTimerTask* m_task; // weak pointer +}; + +} // namespace WebCore + +#endif diff --git a/cc/CCTimerTest.cpp b/cc/CCTimerTest.cpp new file mode 100644 index 0000000..fe65d6e --- /dev/null +++ b/cc/CCTimerTest.cpp @@ -0,0 +1,63 @@ +// 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 "config.h" + +#include "CCTimer.h" + +#include "CCSchedulerTestCommon.h" +#include <gtest/gtest.h> + +using namespace WebCore; +using namespace WebKitTests; + +namespace { + +class CCTimerTest : public testing::Test, public CCTimerClient { +public: + CCTimerTest() : m_flag(false) { } + + void onTimerFired() { m_flag = true; } + +protected: + FakeCCThread m_thread; + bool m_flag; +}; + +TEST_F(CCTimerTest, OneShot) +{ + CCTimer timer(&m_thread, this); + timer.startOneShot(0.001); + EXPECT_TRUE(timer.isActive()); + m_thread.runPendingTask(); + EXPECT_FALSE(timer.isActive()); + EXPECT_TRUE(m_flag); + EXPECT_FALSE(m_thread.hasPendingTask()); +} + +TEST_F(CCTimerTest, StopManually) +{ + CCTimer timer(&m_thread, this); + timer.startOneShot(0.001); + EXPECT_TRUE(timer.isActive()); + timer.stop(); + EXPECT_FALSE(timer.isActive()); + + m_thread.runPendingTask(); + EXPECT_FALSE(m_flag); + EXPECT_FALSE(m_thread.hasPendingTask()); +} + +TEST_F(CCTimerTest, StopByScope) +{ + { + CCTimer timer(&m_thread, this); + timer.startOneShot(0.001); + } + + m_thread.runPendingTask(); + EXPECT_FALSE(m_flag); +} + +} diff --git a/cc/CCTimingFunction.cpp b/cc/CCTimingFunction.cpp new file mode 100644 index 0000000..6446948 --- /dev/null +++ b/cc/CCTimingFunction.cpp @@ -0,0 +1,76 @@ +// Copyright 2012 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 "CCTimingFunction.h" + +#include <wtf/OwnPtr.h> + +namespace { +const double epsilon = 1e-6; +} // namespace + +namespace WebCore { + +CCTimingFunction::CCTimingFunction() +{ +} + +CCTimingFunction::~CCTimingFunction() +{ +} + +double CCTimingFunction::duration() const +{ + return 1.0; +} + +PassOwnPtr<CCCubicBezierTimingFunction> CCCubicBezierTimingFunction::create(double x1, double y1, double x2, double y2) +{ + return adoptPtr(new CCCubicBezierTimingFunction(x1, y1, x2, y2)); +} + +CCCubicBezierTimingFunction::CCCubicBezierTimingFunction(double x1, double y1, double x2, double y2) + : m_curve(x1, y1, x2, y2) +{ +} + +CCCubicBezierTimingFunction::~CCCubicBezierTimingFunction() +{ +} + +float CCCubicBezierTimingFunction::getValue(double x) const +{ + UnitBezier temp(m_curve); + return static_cast<float>(temp.solve(x, epsilon)); +} + +PassOwnPtr<CCAnimationCurve> CCCubicBezierTimingFunction::clone() const +{ + return adoptPtr(new CCCubicBezierTimingFunction(*this)); +} + +// These numbers come from http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag. +PassOwnPtr<CCTimingFunction> CCEaseTimingFunction::create() +{ + return CCCubicBezierTimingFunction::create(0.25, 0.1, 0.25, 1); +} + +PassOwnPtr<CCTimingFunction> CCEaseInTimingFunction::create() +{ + return CCCubicBezierTimingFunction::create(0.42, 0, 1.0, 1); +} + +PassOwnPtr<CCTimingFunction> CCEaseOutTimingFunction::create() +{ + return CCCubicBezierTimingFunction::create(0, 0, 0.58, 1); +} + +PassOwnPtr<CCTimingFunction> CCEaseInOutTimingFunction::create() +{ + return CCCubicBezierTimingFunction::create(0.42, 0, 0.58, 1); +} + +} // namespace WebCore diff --git a/cc/CCTimingFunction.h b/cc/CCTimingFunction.h new file mode 100644 index 0000000..30bba72 --- /dev/null +++ b/cc/CCTimingFunction.h @@ -0,0 +1,64 @@ +// Copyright 2012 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. + +#ifndef CCTimingFunction_h +#define CCTimingFunction_h + +#include "CCAnimationCurve.h" +#include "UnitBezier.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +// See http://www.w3.org/TR/css3-transitions/. +class CCTimingFunction : public CCFloatAnimationCurve { +public: + virtual ~CCTimingFunction(); + + // Partial implementation of CCFloatAnimationCurve. + virtual double duration() const OVERRIDE; + +protected: + CCTimingFunction(); +}; + +class CCCubicBezierTimingFunction : public CCTimingFunction { +public: + static PassOwnPtr<CCCubicBezierTimingFunction> create(double x1, double y1, double x2, double y2); + virtual ~CCCubicBezierTimingFunction(); + + // Partial implementation of CCFloatAnimationCurve. + virtual float getValue(double time) const OVERRIDE; + virtual PassOwnPtr<CCAnimationCurve> clone() const OVERRIDE; + +protected: + CCCubicBezierTimingFunction(double x1, double y1, double x2, double y2); + + UnitBezier m_curve; +}; + +class CCEaseTimingFunction { +public: + static PassOwnPtr<CCTimingFunction> create(); +}; + +class CCEaseInTimingFunction { +public: + static PassOwnPtr<CCTimingFunction> create(); +}; + +class CCEaseOutTimingFunction { +public: + static PassOwnPtr<CCTimingFunction> create(); +}; + +class CCEaseInOutTimingFunction { +public: + static PassOwnPtr<CCTimingFunction> create(); +}; + +} // namespace WebCore + +#endif // CCTimingFunction_h + diff --git a/cc/CCVideoLayerImpl.cpp b/cc/CCVideoLayerImpl.cpp new file mode 100644 index 0000000..3643c1b --- /dev/null +++ b/cc/CCVideoLayerImpl.cpp @@ -0,0 +1,388 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCVideoLayerImpl.h" + +#include "CCIOSurfaceDrawQuad.h" +#include "CCLayerTreeHostImpl.h" +#include "CCProxy.h" +#include "CCQuadSink.h" +#include "CCResourceProvider.h" +#include "CCStreamVideoDrawQuad.h" +#include "CCTextureDrawQuad.h" +#include "CCYUVVideoDrawQuad.h" +#include "Extensions3DChromium.h" +#include "GraphicsContext3D.h" +#include "NotImplemented.h" +#include "TextStream.h" +#include <public/WebVideoFrame.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +CCVideoLayerImpl::CCVideoLayerImpl(int id, WebKit::WebVideoFrameProvider* provider) + : CCLayerImpl(id) + , m_provider(provider) + , m_frame(0) + , m_externalTextureResource(0) +{ + // This matrix is the default transformation for stream textures, and flips on the Y axis. + m_streamTextureMatrix = WebKit::WebTransformationMatrix( + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, 1, 0, + 0, 1, 0, 1); + + // This only happens during a commit on the compositor thread while the main + // thread is blocked. That makes this a thread-safe call to set the video + // frame provider client that does not require a lock. The same is true of + // the call in the destructor. + ASSERT(CCProxy::isMainThreadBlocked()); + m_provider->setVideoFrameProviderClient(this); +} + +CCVideoLayerImpl::~CCVideoLayerImpl() +{ + // See comment in constructor for why this doesn't need a lock. + ASSERT(CCProxy::isMainThreadBlocked()); + if (m_provider) { + m_provider->setVideoFrameProviderClient(0); + m_provider = 0; + } + freePlaneData(layerTreeHostImpl()->resourceProvider()); + +#if !ASSERT_DISABLED + for (unsigned i = 0; i < WebKit::WebVideoFrame::maxPlanes; ++i) + ASSERT(!m_framePlanes[i].resourceId); + ASSERT(!m_externalTextureResource); +#endif +} + +void CCVideoLayerImpl::stopUsingProvider() +{ + // Block the provider from shutting down until this client is done + // using the frame. + MutexLocker locker(m_providerMutex); + ASSERT(!m_frame); + m_provider = 0; +} + +// Convert WebKit::WebVideoFrame::Format to GraphicsContext3D's format enum values. +static GC3Denum convertVFCFormatToGC3DFormat(const WebKit::WebVideoFrame& frame) +{ + switch (frame.format()) { + case WebKit::WebVideoFrame::FormatYV12: + case WebKit::WebVideoFrame::FormatYV16: + return GraphicsContext3D::LUMINANCE; + case WebKit::WebVideoFrame::FormatNativeTexture: + return frame.textureTarget(); + case WebKit::WebVideoFrame::FormatInvalid: + case WebKit::WebVideoFrame::FormatRGB32: + case WebKit::WebVideoFrame::FormatEmpty: + case WebKit::WebVideoFrame::FormatI420: + notImplemented(); + } + return GraphicsContext3D::INVALID_VALUE; +} + +void CCVideoLayerImpl::willDraw(CCResourceProvider* resourceProvider) +{ + ASSERT(CCProxy::isImplThread()); + CCLayerImpl::willDraw(resourceProvider); + + // Explicitly lock and unlock the provider mutex so it can be held from + // willDraw to didDraw. Since the compositor thread is in the middle of + // drawing, the layer will not be destroyed before didDraw is called. + // Therefore, the only thing that will prevent this lock from being released + // is the GPU process locking it. As the GPU process can't cause the + // destruction of the provider (calling stopUsingProvider), holding this + // lock should not cause a deadlock. + m_providerMutex.lock(); + + willDrawInternal(resourceProvider); + freeUnusedPlaneData(resourceProvider); + + if (!m_frame) + m_providerMutex.unlock(); +} + +void CCVideoLayerImpl::willDrawInternal(CCResourceProvider* resourceProvider) +{ + ASSERT(CCProxy::isImplThread()); + ASSERT(!m_externalTextureResource); + + if (!m_provider) { + m_frame = 0; + return; + } + + m_frame = m_provider->getCurrentFrame(); + + if (!m_frame) + return; + + m_format = convertVFCFormatToGC3DFormat(*m_frame); + + if (m_format == GraphicsContext3D::INVALID_VALUE) { + m_provider->putCurrentFrame(m_frame); + m_frame = 0; + return; + } + + if (m_frame->planes() > WebKit::WebVideoFrame::maxPlanes) { + m_provider->putCurrentFrame(m_frame); + m_frame = 0; + return; + } + + if (!allocatePlaneData(resourceProvider)) { + m_provider->putCurrentFrame(m_frame); + m_frame = 0; + return; + } + + if (!copyPlaneData(resourceProvider)) { + m_provider->putCurrentFrame(m_frame); + m_frame = 0; + return; + } + + if (m_format == GraphicsContext3D::TEXTURE_2D) + m_externalTextureResource = resourceProvider->createResourceFromExternalTexture(m_frame->textureId()); +} + +void CCVideoLayerImpl::appendQuads(CCQuadSink& quadSink, bool&) +{ + ASSERT(CCProxy::isImplThread()); + + if (!m_frame) + return; + + CCSharedQuadState* sharedQuadState = quadSink.useSharedQuadState(createSharedQuadState()); + appendDebugBorderQuad(quadSink, sharedQuadState); + + // FIXME: When we pass quads out of process, we need to double-buffer, or + // otherwise synchonize use of all textures in the quad. + + IntRect quadRect(IntPoint(), contentBounds()); + + switch (m_format) { + case GraphicsContext3D::LUMINANCE: { + // YUV software decoder. + const FramePlane& yPlane = m_framePlanes[WebKit::WebVideoFrame::yPlane]; + const FramePlane& uPlane = m_framePlanes[WebKit::WebVideoFrame::uPlane]; + const FramePlane& vPlane = m_framePlanes[WebKit::WebVideoFrame::vPlane]; + OwnPtr<CCYUVVideoDrawQuad> yuvVideoQuad = CCYUVVideoDrawQuad::create(sharedQuadState, quadRect, yPlane, uPlane, vPlane); + quadSink.append(yuvVideoQuad.release()); + break; + } + case GraphicsContext3D::RGBA: { + // RGBA software decoder. + const FramePlane& plane = m_framePlanes[WebKit::WebVideoFrame::rgbPlane]; + float widthScaleFactor = static_cast<float>(plane.visibleSize.width()) / plane.size.width(); + + bool premultipliedAlpha = true; + FloatRect uvRect(0, 0, widthScaleFactor, 1); + bool flipped = false; + OwnPtr<CCTextureDrawQuad> textureQuad = CCTextureDrawQuad::create(sharedQuadState, quadRect, plane.resourceId, premultipliedAlpha, uvRect, flipped); + quadSink.append(textureQuad.release()); + break; + } + case GraphicsContext3D::TEXTURE_2D: { + // NativeTexture hardware decoder. + bool premultipliedAlpha = true; + FloatRect uvRect(0, 0, 1, 1); + bool flipped = false; + OwnPtr<CCTextureDrawQuad> textureQuad = CCTextureDrawQuad::create(sharedQuadState, quadRect, m_externalTextureResource, premultipliedAlpha, uvRect, flipped); + quadSink.append(textureQuad.release()); + break; + } + case Extensions3D::TEXTURE_RECTANGLE_ARB: { + IntSize textureSize(m_frame->width(), m_frame->height()); + OwnPtr<CCIOSurfaceDrawQuad> ioSurfaceQuad = CCIOSurfaceDrawQuad::create(sharedQuadState, quadRect, textureSize, m_frame->textureId(), CCIOSurfaceDrawQuad::Unflipped); + quadSink.append(ioSurfaceQuad.release()); + break; + } + case Extensions3DChromium::GL_TEXTURE_EXTERNAL_OES: { + // StreamTexture hardware decoder. + OwnPtr<CCStreamVideoDrawQuad> streamVideoQuad = CCStreamVideoDrawQuad::create(sharedQuadState, quadRect, m_frame->textureId(), m_streamTextureMatrix); + quadSink.append(streamVideoQuad.release()); + break; + } + default: + CRASH(); // Someone updated convertVFCFormatToGC3DFormat above but update this! + } +} + +void CCVideoLayerImpl::didDraw(CCResourceProvider* resourceProvider) +{ + ASSERT(CCProxy::isImplThread()); + CCLayerImpl::didDraw(resourceProvider); + + if (!m_frame) + return; + + if (m_format == GraphicsContext3D::TEXTURE_2D) { + ASSERT(m_externalTextureResource); + // FIXME: the following assert will not be true when sending resources to a + // parent compositor. We will probably need to hold on to m_frame for + // longer, and have several "current frames" in the pipeline. + ASSERT(!resourceProvider->inUseByConsumer(m_externalTextureResource)); + resourceProvider->deleteResource(m_externalTextureResource); + m_externalTextureResource = 0; + } + + m_provider->putCurrentFrame(m_frame); + m_frame = 0; + + m_providerMutex.unlock(); +} + +static int videoFrameDimension(int originalDimension, unsigned plane, int format) +{ + if (format == WebKit::WebVideoFrame::FormatYV12 && plane != WebKit::WebVideoFrame::yPlane) + return originalDimension / 2; + return originalDimension; +} + +static bool hasPaddingBytes(const WebKit::WebVideoFrame& frame, unsigned plane) +{ + return frame.stride(plane) > videoFrameDimension(frame.width(), plane, frame.format()); +} + +IntSize CCVideoLayerImpl::computeVisibleSize(const WebKit::WebVideoFrame& frame, unsigned plane) +{ + int visibleWidth = videoFrameDimension(frame.width(), plane, frame.format()); + int originalWidth = visibleWidth; + int visibleHeight = videoFrameDimension(frame.height(), plane, frame.format()); + + // When there are dead pixels at the edge of the texture, decrease + // the frame width by 1 to prevent the rightmost pixels from + // interpolating with the dead pixels. + if (hasPaddingBytes(frame, plane)) + --visibleWidth; + + // In YV12, every 2x2 square of Y values corresponds to one U and + // one V value. If we decrease the width of the UV plane, we must decrease the + // width of the Y texture by 2 for proper alignment. This must happen + // always, even if Y's texture does not have padding bytes. + if (plane == WebKit::WebVideoFrame::yPlane && frame.format() == WebKit::WebVideoFrame::FormatYV12) { + if (hasPaddingBytes(frame, WebKit::WebVideoFrame::uPlane)) + visibleWidth = originalWidth - 2; + } + + return IntSize(visibleWidth, visibleHeight); +} + +bool CCVideoLayerImpl::FramePlane::allocateData(CCResourceProvider* resourceProvider) +{ + if (resourceId) + return true; + + resourceId = resourceProvider->createResource(CCRenderer::ImplPool, size, format, CCResourceProvider::TextureUsageAny); + return resourceId; +} + +void CCVideoLayerImpl::FramePlane::freeData(CCResourceProvider* resourceProvider) +{ + if (!resourceId) + return; + + resourceProvider->deleteResource(resourceId); + resourceId = 0; +} + +bool CCVideoLayerImpl::allocatePlaneData(CCResourceProvider* resourceProvider) +{ + int maxTextureSize = resourceProvider->maxTextureSize(); + for (unsigned planeIndex = 0; planeIndex < m_frame->planes(); ++planeIndex) { + CCVideoLayerImpl::FramePlane& plane = m_framePlanes[planeIndex]; + + IntSize requiredTextureSize(m_frame->stride(planeIndex), videoFrameDimension(m_frame->height(), planeIndex, m_frame->format())); + // FIXME: Remove the test against maxTextureSize when tiled layers are implemented. + if (requiredTextureSize.isZero() || requiredTextureSize.width() > maxTextureSize || requiredTextureSize.height() > maxTextureSize) + return false; + + if (plane.size != requiredTextureSize || plane.format != m_format) { + plane.freeData(resourceProvider); + plane.size = requiredTextureSize; + plane.format = m_format; + } + + if (!plane.resourceId) { + if (!plane.allocateData(resourceProvider)) + return false; + plane.visibleSize = computeVisibleSize(*m_frame, planeIndex); + } + } + return true; +} + +bool CCVideoLayerImpl::copyPlaneData(CCResourceProvider* resourceProvider) +{ + size_t softwarePlaneCount = m_frame->planes(); + if (!softwarePlaneCount) + return true; + + for (size_t softwarePlaneIndex = 0; softwarePlaneIndex < softwarePlaneCount; ++softwarePlaneIndex) { + CCVideoLayerImpl::FramePlane& plane = m_framePlanes[softwarePlaneIndex]; + const uint8_t* softwarePlanePixels = static_cast<const uint8_t*>(m_frame->data(softwarePlaneIndex)); + IntRect planeRect(IntPoint(), plane.size); + resourceProvider->upload(plane.resourceId, softwarePlanePixels, planeRect, planeRect, IntSize()); + } + return true; +} + +void CCVideoLayerImpl::freePlaneData(CCResourceProvider* resourceProvider) +{ + for (unsigned i = 0; i < WebKit::WebVideoFrame::maxPlanes; ++i) + m_framePlanes[i].freeData(resourceProvider); +} + +void CCVideoLayerImpl::freeUnusedPlaneData(CCResourceProvider* resourceProvider) +{ + unsigned firstUnusedPlane = m_frame ? m_frame->planes() : 0; + for (unsigned i = firstUnusedPlane; i < WebKit::WebVideoFrame::maxPlanes; ++i) + m_framePlanes[i].freeData(resourceProvider); +} + +void CCVideoLayerImpl::didReceiveFrame() +{ + setNeedsRedraw(); +} + +void CCVideoLayerImpl::didUpdateMatrix(const float matrix[16]) +{ + m_streamTextureMatrix = WebKit::WebTransformationMatrix( + matrix[0], matrix[1], matrix[2], matrix[3], + matrix[4], matrix[5], matrix[6], matrix[7], + matrix[8], matrix[9], matrix[10], matrix[11], + matrix[12], matrix[13], matrix[14], matrix[15]); + setNeedsRedraw(); +} + +void CCVideoLayerImpl::didLoseContext() +{ + freePlaneData(layerTreeHostImpl()->resourceProvider()); +} + +void CCVideoLayerImpl::setNeedsRedraw() +{ + layerTreeHostImpl()->setNeedsRedraw(); +} + +void CCVideoLayerImpl::dumpLayerProperties(TextStream& ts, int indent) const +{ + writeIndent(ts, indent); + ts << "video layer\n"; + CCLayerImpl::dumpLayerProperties(ts, indent); +} + +} + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CCVideoLayerImpl.h b/cc/CCVideoLayerImpl.h new file mode 100644 index 0000000..17fc030 --- /dev/null +++ b/cc/CCVideoLayerImpl.h @@ -0,0 +1,89 @@ +// Copyright 2012 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. + +#ifndef CCVideoLayerImpl_h +#define CCVideoLayerImpl_h + +#include "CCLayerImpl.h" +#include "GraphicsContext3D.h" +#include "IntSize.h" +#include <public/WebTransformationMatrix.h> +#include <public/WebVideoFrameProvider.h> + +namespace WebKit { +class WebVideoFrame; +} + +namespace WebCore { + +class CCLayerTreeHostImpl; +class CCVideoLayerImpl; + +class CCVideoLayerImpl : public CCLayerImpl + , public WebKit::WebVideoFrameProvider::Client { +public: + static PassOwnPtr<CCVideoLayerImpl> create(int id, WebKit::WebVideoFrameProvider* provider) + { + return adoptPtr(new CCVideoLayerImpl(id, provider)); + } + virtual ~CCVideoLayerImpl(); + + virtual void willDraw(CCResourceProvider*) OVERRIDE; + virtual void appendQuads(CCQuadSink&, bool& hadMissingTiles) OVERRIDE; + virtual void didDraw(CCResourceProvider*) OVERRIDE; + + virtual void dumpLayerProperties(TextStream&, int indent) const OVERRIDE; + + Mutex& providerMutex() { return m_providerMutex; } + + // WebKit::WebVideoFrameProvider::Client implementation. + virtual void stopUsingProvider(); // Callable on any thread. + virtual void didReceiveFrame(); // Callable on impl thread. + virtual void didUpdateMatrix(const float*); // Callable on impl thread. + + virtual void didLoseContext() OVERRIDE; + + void setNeedsRedraw(); + + struct FramePlane { + CCResourceProvider::ResourceId resourceId; + IntSize size; + GC3Denum format; + IntSize visibleSize; + + FramePlane() : resourceId(0) { } + + bool allocateData(CCResourceProvider*); + void freeData(CCResourceProvider*); + }; + +private: + CCVideoLayerImpl(int, WebKit::WebVideoFrameProvider*); + + static IntSize computeVisibleSize(const WebKit::WebVideoFrame&, unsigned plane); + virtual const char* layerTypeAsString() const OVERRIDE { return "VideoLayer"; } + + void willDrawInternal(CCResourceProvider*); + bool allocatePlaneData(CCResourceProvider*); + bool copyPlaneData(CCResourceProvider*); + void freePlaneData(CCResourceProvider*); + void freeUnusedPlaneData(CCResourceProvider*); + + // Guards the destruction of m_provider and the frame that it provides + Mutex m_providerMutex; + WebKit::WebVideoFrameProvider* m_provider; + + WebKit::WebTransformationMatrix m_streamTextureMatrix; + + WebKit::WebVideoFrame* m_frame; + GC3Denum m_format; + CCResourceProvider::ResourceId m_externalTextureResource; + + // Each index in this array corresponds to a plane in WebKit::WebVideoFrame. + FramePlane m_framePlanes[WebKit::WebVideoFrame::maxPlanes]; +}; + +} + +#endif // CCVideoLayerImpl_h diff --git a/cc/CCYUVVideoDrawQuad.cpp b/cc/CCYUVVideoDrawQuad.cpp new file mode 100644 index 0000000..66e0fc1 --- /dev/null +++ b/cc/CCYUVVideoDrawQuad.cpp @@ -0,0 +1,30 @@ +// Copyright 2012 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 "CCYUVVideoDrawQuad.h" + +namespace WebCore { + +PassOwnPtr<CCYUVVideoDrawQuad> CCYUVVideoDrawQuad::create(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, const CCVideoLayerImpl::FramePlane& yPlane, const CCVideoLayerImpl::FramePlane& uPlane, const CCVideoLayerImpl::FramePlane& vPlane) +{ + return adoptPtr(new CCYUVVideoDrawQuad(sharedQuadState, quadRect, yPlane, uPlane, vPlane)); +} + +CCYUVVideoDrawQuad::CCYUVVideoDrawQuad(const CCSharedQuadState* sharedQuadState, const IntRect& quadRect, const CCVideoLayerImpl::FramePlane& yPlane, const CCVideoLayerImpl::FramePlane& uPlane, const CCVideoLayerImpl::FramePlane& vPlane) + : CCDrawQuad(sharedQuadState, CCDrawQuad::YUVVideoContent, quadRect) + , m_yPlane(yPlane) + , m_uPlane(uPlane) + , m_vPlane(vPlane) +{ +} + +const CCYUVVideoDrawQuad* CCYUVVideoDrawQuad::materialCast(const CCDrawQuad* quad) +{ + ASSERT(quad->material() == CCDrawQuad::YUVVideoContent); + return static_cast<const CCYUVVideoDrawQuad*>(quad); +} + +} diff --git a/cc/CCYUVVideoDrawQuad.h b/cc/CCYUVVideoDrawQuad.h new file mode 100644 index 0000000..240922f --- /dev/null +++ b/cc/CCYUVVideoDrawQuad.h @@ -0,0 +1,34 @@ +// Copyright 2012 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. + +#ifndef CCYUVVideoDrawQuad_h +#define CCYUVVideoDrawQuad_h + +#include "CCDrawQuad.h" +#include "CCVideoLayerImpl.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCYUVVideoDrawQuad : public CCDrawQuad { + WTF_MAKE_NONCOPYABLE(CCYUVVideoDrawQuad); +public: + static PassOwnPtr<CCYUVVideoDrawQuad> create(const CCSharedQuadState*, const IntRect&, const CCVideoLayerImpl::FramePlane& yPlane, const CCVideoLayerImpl::FramePlane& uPlane, const CCVideoLayerImpl::FramePlane& vPlane); + + const CCVideoLayerImpl::FramePlane& yPlane() const { return m_yPlane; } + const CCVideoLayerImpl::FramePlane& uPlane() const { return m_uPlane; } + const CCVideoLayerImpl::FramePlane& vPlane() const { return m_vPlane; } + + static const CCYUVVideoDrawQuad* materialCast(const CCDrawQuad*); +private: + CCYUVVideoDrawQuad(const CCSharedQuadState*, const IntRect&, const CCVideoLayerImpl::FramePlane& yPlane, const CCVideoLayerImpl::FramePlane& uPlane, const CCVideoLayerImpl::FramePlane& vPlane); + + CCVideoLayerImpl::FramePlane m_yPlane; + CCVideoLayerImpl::FramePlane m_uPlane; + CCVideoLayerImpl::FramePlane m_vPlane; +}; + +} + +#endif diff --git a/cc/CanvasLayerTextureUpdater.cpp b/cc/CanvasLayerTextureUpdater.cpp new file mode 100644 index 0000000..dd63b97 --- /dev/null +++ b/cc/CanvasLayerTextureUpdater.cpp @@ -0,0 +1,70 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "CanvasLayerTextureUpdater.h" + +#include "CCRenderingStats.h" +#include "FloatRect.h" +#include "LayerPainterChromium.h" +#include "SkCanvas.h" +#include "SkPaint.h" +#include "SkRect.h" +#include "SkiaUtils.h" +#include "TraceEvent.h" +#include <wtf/CurrentTime.h> + +namespace WebCore { + +CanvasLayerTextureUpdater::CanvasLayerTextureUpdater(PassOwnPtr<LayerPainterChromium> painter) + : m_painter(painter) +{ +} + +CanvasLayerTextureUpdater::~CanvasLayerTextureUpdater() +{ +} + +void CanvasLayerTextureUpdater::paintContents(SkCanvas* canvas, const IntRect& contentRect, float contentsWidthScale, float contentsHeightScale, IntRect& resultingOpaqueRect, CCRenderingStats& stats) +{ + TRACE_EVENT0("cc", "CanvasLayerTextureUpdater::paintContents"); + canvas->save(); + canvas->translate(WebCoreFloatToSkScalar(-contentRect.x()), WebCoreFloatToSkScalar(-contentRect.y())); + + IntRect layerRect = contentRect; + + if (contentsWidthScale != 1 || contentsHeightScale != 1) { + canvas->scale(WebCoreFloatToSkScalar(contentsWidthScale), WebCoreFloatToSkScalar(contentsHeightScale)); + + FloatRect rect = contentRect; + rect.scale(1 / contentsWidthScale, 1 / contentsHeightScale); + layerRect = enclosingIntRect(rect); + } + + SkPaint paint; + paint.setAntiAlias(false); + paint.setXfermodeMode(SkXfermode::kClear_Mode); + SkRect layerSkRect = SkRect::MakeXYWH(layerRect.x(), layerRect.y(), layerRect.width(), layerRect.height()); + canvas->drawRect(layerSkRect, paint); + canvas->clipRect(layerSkRect); + + FloatRect opaqueLayerRect; + double paintBeginTime = monotonicallyIncreasingTime(); + m_painter->paint(canvas, layerRect, opaqueLayerRect); + stats.totalPaintTimeInSeconds += monotonicallyIncreasingTime() - paintBeginTime; + canvas->restore(); + + FloatRect opaqueContentRect = opaqueLayerRect; + opaqueContentRect.scale(contentsWidthScale, contentsHeightScale); + resultingOpaqueRect = enclosedIntRect(opaqueContentRect); + + m_contentRect = contentRect; +} + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/CanvasLayerTextureUpdater.h b/cc/CanvasLayerTextureUpdater.h new file mode 100644 index 0000000..051f9fc --- /dev/null +++ b/cc/CanvasLayerTextureUpdater.h @@ -0,0 +1,39 @@ +// 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. + + +#ifndef CanvasLayerTextureUpdater_h +#define CanvasLayerTextureUpdater_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "LayerTextureUpdater.h" + +class SkCanvas; + +namespace WebCore { + +class LayerPainterChromium; + +// Base class for BitmapCanvasLayerTextureUpdater and +// SkPictureCanvasLayerTextureUpdater that reduces code duplication between +// their respective paintContents implementations. +class CanvasLayerTextureUpdater : public LayerTextureUpdater { +public: + virtual ~CanvasLayerTextureUpdater(); + +protected: + explicit CanvasLayerTextureUpdater(PassOwnPtr<LayerPainterChromium>); + + void paintContents(SkCanvas*, const IntRect& contentRect, float contentsWidthScale, float contentsHeightScale, IntRect& resultingOpaqueRect, CCRenderingStats&); + const IntRect& contentRect() const { return m_contentRect; } + +private: + IntRect m_contentRect; + OwnPtr<LayerPainterChromium> m_painter; +}; + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) +#endif // CanvasLayerTextureUpdater_h diff --git a/cc/ContentLayerChromium.cpp b/cc/ContentLayerChromium.cpp new file mode 100644 index 0000000..7465ed4 --- /dev/null +++ b/cc/ContentLayerChromium.cpp @@ -0,0 +1,107 @@ +// 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "ContentLayerChromium.h" + +#include "BitmapCanvasLayerTextureUpdater.h" +#include "BitmapSkPictureCanvasLayerTextureUpdater.h" +#include "CCLayerTreeHost.h" +#include "CCSettings.h" +#include "FrameBufferSkPictureCanvasLayerTextureUpdater.h" +#include "LayerPainterChromium.h" +#include <public/Platform.h> +#include <wtf/CurrentTime.h> + +namespace WebCore { + +ContentLayerPainter::ContentLayerPainter(ContentLayerDelegate* delegate) + : m_delegate(delegate) +{ +} + +PassOwnPtr<ContentLayerPainter> ContentLayerPainter::create(ContentLayerDelegate* delegate) +{ + return adoptPtr(new ContentLayerPainter(delegate)); +} + +void ContentLayerPainter::paint(SkCanvas* canvas, const IntRect& contentRect, FloatRect& opaque) +{ + double paintStart = currentTime(); + m_delegate->paintContents(canvas, contentRect, opaque); + double paintEnd = currentTime(); + double pixelsPerSec = (contentRect.width() * contentRect.height()) / (paintEnd - paintStart); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.AccelContentPaintDurationMS", (paintEnd - paintStart) * 1000, 0, 120, 30); + WebKit::Platform::current()->histogramCustomCounts("Renderer4.AccelContentPaintMegapixPerSecond", pixelsPerSec / 1000000, 10, 210, 30); +} + +PassRefPtr<ContentLayerChromium> ContentLayerChromium::create(ContentLayerDelegate* delegate) +{ + return adoptRef(new ContentLayerChromium(delegate)); +} + +ContentLayerChromium::ContentLayerChromium(ContentLayerDelegate* delegate) + : TiledLayerChromium() + , m_delegate(delegate) +{ +} + +ContentLayerChromium::~ContentLayerChromium() +{ +} + +bool ContentLayerChromium::drawsContent() const +{ + return TiledLayerChromium::drawsContent() && m_delegate; +} + +void ContentLayerChromium::setTexturePriorities(const CCPriorityCalculator& priorityCalc) +{ + // Update the tile data before creating all the layer's tiles. + updateTileSizeAndTilingOption(); + + TiledLayerChromium::setTexturePriorities(priorityCalc); +} + +void ContentLayerChromium::update(CCTextureUpdateQueue& queue, const CCOcclusionTracker* occlusion, CCRenderingStats& stats) +{ + createTextureUpdaterIfNeeded(); + TiledLayerChromium::update(queue, occlusion, stats); + m_needsDisplay = false; +} + +bool ContentLayerChromium::needMoreUpdates() +{ + return needsIdlePaint(); +} + +void ContentLayerChromium::createTextureUpdaterIfNeeded() +{ + if (m_textureUpdater) + return; + if (layerTreeHost()->settings().acceleratePainting) + m_textureUpdater = FrameBufferSkPictureCanvasLayerTextureUpdater::create(ContentLayerPainter::create(m_delegate)); + else if (CCSettings::perTilePaintingEnabled()) + m_textureUpdater = BitmapSkPictureCanvasLayerTextureUpdater::create(ContentLayerPainter::create(m_delegate)); + else + m_textureUpdater = BitmapCanvasLayerTextureUpdater::create(ContentLayerPainter::create(m_delegate)); + m_textureUpdater->setOpaque(opaque()); + + GC3Denum textureFormat = layerTreeHost()->rendererCapabilities().bestTextureFormat; + setTextureFormat(textureFormat); + setSampledTexelFormat(textureUpdater()->sampledTexelFormat(textureFormat)); +} + +void ContentLayerChromium::setOpaque(bool opaque) +{ + LayerChromium::setOpaque(opaque); + if (m_textureUpdater) + m_textureUpdater->setOpaque(opaque); +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/ContentLayerChromium.h b/cc/ContentLayerChromium.h new file mode 100644 index 0000000..fa68d11 --- /dev/null +++ b/cc/ContentLayerChromium.h @@ -0,0 +1,74 @@ +// 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. + + +#ifndef ContentLayerChromium_h +#define ContentLayerChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "LayerPainterChromium.h" +#include "TiledLayerChromium.h" + +class SkCanvas; + +namespace WebCore { + +class FloatRect; +class IntRect; +class LayerTextureUpdater; + +class ContentLayerDelegate { +public: + virtual void paintContents(SkCanvas*, const IntRect& clip, FloatRect& opaque) = 0; + +protected: + virtual ~ContentLayerDelegate() { } +}; + +class ContentLayerPainter : public LayerPainterChromium { + WTF_MAKE_NONCOPYABLE(ContentLayerPainter); +public: + static PassOwnPtr<ContentLayerPainter> create(ContentLayerDelegate*); + + virtual void paint(SkCanvas*, const IntRect& contentRect, FloatRect& opaque) OVERRIDE; + +private: + explicit ContentLayerPainter(ContentLayerDelegate*); + + ContentLayerDelegate* m_delegate; +}; + +// A layer that renders its contents into an SkCanvas. +class ContentLayerChromium : public TiledLayerChromium { +public: + static PassRefPtr<ContentLayerChromium> create(ContentLayerDelegate*); + + virtual ~ContentLayerChromium(); + + void clearDelegate() { m_delegate = 0; } + + virtual bool drawsContent() const OVERRIDE; + virtual void setTexturePriorities(const CCPriorityCalculator&) OVERRIDE; + virtual void update(CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&) OVERRIDE; + virtual bool needMoreUpdates() OVERRIDE; + + virtual void setOpaque(bool) OVERRIDE; + +protected: + explicit ContentLayerChromium(ContentLayerDelegate*); + + +private: + virtual LayerTextureUpdater* textureUpdater() const OVERRIDE { return m_textureUpdater.get(); } + virtual void createTextureUpdaterIfNeeded() OVERRIDE; + + ContentLayerDelegate* m_delegate; + RefPtr<LayerTextureUpdater> m_textureUpdater; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif @@ -0,0 +1,7 @@ +include_rules = [ + "+third_party/khronos/GLES2/gl2.h", + "+third_party/khronos/GLES2/gl2ext.h", + "+third_party/WebKit/Source/WebCore/platform/graphics/Region.h", + "+third_party/WebKit/Source/WebCore/platform/graphics/gpu/TilingData.h", + "+third_party/WebKit/Source/WebCore/platform/graphics/UnitBezier.h", +] diff --git a/cc/FrameBufferSkPictureCanvasLayerTextureUpdater.cpp b/cc/FrameBufferSkPictureCanvasLayerTextureUpdater.cpp new file mode 100644 index 0000000..c3b5dbb --- /dev/null +++ b/cc/FrameBufferSkPictureCanvasLayerTextureUpdater.cpp @@ -0,0 +1,114 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "FrameBufferSkPictureCanvasLayerTextureUpdater.h" + +#include "CCProxy.h" +#include "LayerPainterChromium.h" +#include "SkCanvas.h" +#include "SkGpuDevice.h" +#include <public/WebGraphicsContext3D.h> +#include <public/WebSharedGraphicsContext3D.h> + +using WebKit::WebGraphicsContext3D; +using WebKit::WebSharedGraphicsContext3D; + +namespace WebCore { + +static PassOwnPtr<SkCanvas> createAcceleratedCanvas(GrContext* grContext, + IntSize canvasSize, + unsigned textureId) +{ + GrPlatformTextureDesc textureDesc; + textureDesc.fFlags = kRenderTarget_GrPlatformTextureFlag; + textureDesc.fWidth = canvasSize.width(); + textureDesc.fHeight = canvasSize.height(); + textureDesc.fConfig = kSkia8888_PM_GrPixelConfig; + textureDesc.fTextureHandle = textureId; + SkAutoTUnref<GrTexture> target(grContext->createPlatformTexture(textureDesc)); + SkAutoTUnref<SkDevice> device(new SkGpuDevice(grContext, target.get())); + return adoptPtr(new SkCanvas(device.get())); +} + +FrameBufferSkPictureCanvasLayerTextureUpdater::Texture::Texture(FrameBufferSkPictureCanvasLayerTextureUpdater* textureUpdater, PassOwnPtr<CCPrioritizedTexture> texture) + : LayerTextureUpdater::Texture(texture) + , m_textureUpdater(textureUpdater) +{ +} + +FrameBufferSkPictureCanvasLayerTextureUpdater::Texture::~Texture() +{ +} + +void FrameBufferSkPictureCanvasLayerTextureUpdater::Texture::updateRect(CCResourceProvider* resourceProvider, const IntRect& sourceRect, const IntSize& destOffset) +{ + WebGraphicsContext3D* sharedContext = CCProxy::hasImplThread() ? WebSharedGraphicsContext3D::compositorThreadContext() : WebSharedGraphicsContext3D::mainThreadContext(); + GrContext* sharedGrContext = CCProxy::hasImplThread() ? WebSharedGraphicsContext3D::compositorThreadGrContext() : WebSharedGraphicsContext3D::mainThreadGrContext(); + if (!sharedContext || !sharedGrContext) + return; + textureUpdater()->updateTextureRect(sharedContext, sharedGrContext, resourceProvider, texture(), sourceRect, destOffset); +} + +PassRefPtr<FrameBufferSkPictureCanvasLayerTextureUpdater> FrameBufferSkPictureCanvasLayerTextureUpdater::create(PassOwnPtr<LayerPainterChromium> painter) +{ + return adoptRef(new FrameBufferSkPictureCanvasLayerTextureUpdater(painter)); +} + +FrameBufferSkPictureCanvasLayerTextureUpdater::FrameBufferSkPictureCanvasLayerTextureUpdater(PassOwnPtr<LayerPainterChromium> painter) + : SkPictureCanvasLayerTextureUpdater(painter) +{ +} + +FrameBufferSkPictureCanvasLayerTextureUpdater::~FrameBufferSkPictureCanvasLayerTextureUpdater() +{ +} + +PassOwnPtr<LayerTextureUpdater::Texture> FrameBufferSkPictureCanvasLayerTextureUpdater::createTexture(CCPrioritizedTextureManager* manager) +{ + return adoptPtr(new Texture(this, CCPrioritizedTexture::create(manager))); +} + +LayerTextureUpdater::SampledTexelFormat FrameBufferSkPictureCanvasLayerTextureUpdater::sampledTexelFormat(GC3Denum textureFormat) +{ + // Here we directly render to the texture, so the component order is always correct. + return LayerTextureUpdater::SampledTexelFormatRGBA; +} + +void FrameBufferSkPictureCanvasLayerTextureUpdater::updateTextureRect(WebGraphicsContext3D* context, GrContext* grContext, CCResourceProvider* resourceProvider, CCPrioritizedTexture* texture, const IntRect& sourceRect, const IntSize& destOffset) +{ + // Make sure ganesh uses the correct GL context. + context->makeContextCurrent(); + + texture->acquireBackingTexture(resourceProvider); + CCResourceProvider::ScopedWriteLockGL lock(resourceProvider, texture->resourceId()); + // Create an accelerated canvas to draw on. + OwnPtr<SkCanvas> canvas = createAcceleratedCanvas(grContext, texture->size(), lock.textureId()); + + // The compositor expects the textures to be upside-down so it can flip + // the final composited image. Ganesh renders the image upright so we + // need to do a y-flip. + canvas->translate(0.0, texture->size().height()); + canvas->scale(1.0, -1.0); + // Clip to the destination on the texture that must be updated. + canvas->clipRect(SkRect::MakeXYWH(destOffset.width(), destOffset.height(), sourceRect.width(), sourceRect.height())); + // Translate the origin of contentRect to that of destRect. + // Note that destRect is defined relative to sourceRect. + canvas->translate(contentRect().x() - sourceRect.x() + destOffset.width(), + contentRect().y() - sourceRect.y() + destOffset.height()); + drawPicture(canvas.get()); + + // Flush ganesh context so that all the rendered stuff appears on the texture. + grContext->flush(); + + // Flush the GL context so rendering results from this context are visible in the compositor's context. + context->flush(); +} + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/FrameBufferSkPictureCanvasLayerTextureUpdater.h b/cc/FrameBufferSkPictureCanvasLayerTextureUpdater.h new file mode 100644 index 0000000..5a9452f --- /dev/null +++ b/cc/FrameBufferSkPictureCanvasLayerTextureUpdater.h @@ -0,0 +1,52 @@ +// 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. + + +#ifndef FrameBufferSkPictureCanvasLayerTextureUpdater_h +#define FrameBufferSkPictureCanvasLayerTextureUpdater_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "SkPictureCanvasLayerTextureUpdater.h" + +class GrContext; + +namespace WebKit { +class WebGraphicsContext3D; +class WebSharedGraphicsContext3D; +} + +namespace WebCore { + +// This class records the contentRect into an SkPicture, then uses accelerated +// drawing to update the texture. The accelerated drawing goes to an +// intermediate framebuffer and then is copied to the destination texture once done. +class FrameBufferSkPictureCanvasLayerTextureUpdater : public SkPictureCanvasLayerTextureUpdater { +public: + class Texture : public LayerTextureUpdater::Texture { + public: + Texture(FrameBufferSkPictureCanvasLayerTextureUpdater*, PassOwnPtr<CCPrioritizedTexture>); + virtual ~Texture(); + + virtual void updateRect(CCResourceProvider*, const IntRect& sourceRect, const IntSize& destOffset) OVERRIDE; + + private: + FrameBufferSkPictureCanvasLayerTextureUpdater* textureUpdater() { return m_textureUpdater; } + + FrameBufferSkPictureCanvasLayerTextureUpdater* m_textureUpdater; + }; + + static PassRefPtr<FrameBufferSkPictureCanvasLayerTextureUpdater> create(PassOwnPtr<LayerPainterChromium>); + virtual ~FrameBufferSkPictureCanvasLayerTextureUpdater(); + + virtual PassOwnPtr<LayerTextureUpdater::Texture> createTexture(CCPrioritizedTextureManager*) OVERRIDE; + virtual SampledTexelFormat sampledTexelFormat(GC3Denum textureFormat) OVERRIDE; + void updateTextureRect(WebKit::WebGraphicsContext3D*, GrContext*, CCResourceProvider*, CCPrioritizedTexture*, const IntRect& sourceRect, const IntSize& destOffset); + +private: + explicit FrameBufferSkPictureCanvasLayerTextureUpdater(PassOwnPtr<LayerPainterChromium>); +}; +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) +#endif // FrameBufferSkPictureCanvasLayerTextureUpdater_h diff --git a/cc/GeometryBinding.cpp b/cc/GeometryBinding.cpp new file mode 100644 index 0000000..65ee243 --- /dev/null +++ b/cc/GeometryBinding.cpp @@ -0,0 +1,61 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "GeometryBinding.h" + +#include "CCRendererGL.h" // For the GLC() macro. +#include "GraphicsContext3D.h" +#include <public/WebGraphicsContext3D.h> + +namespace WebCore { + +GeometryBinding::GeometryBinding(WebKit::WebGraphicsContext3D* context, const FloatRect& quadVertexRect) + : m_context(context) + , m_quadVerticesVbo(0) + , m_quadElementsVbo(0) + , m_initialized(false) +{ + // Vertex positions and texture coordinates for the 4 corners of a 1x1 quad. + float vertices[] = { quadVertexRect.x(), quadVertexRect.maxY(), 0.0f, 0.0f, 1.0f, + quadVertexRect.x(), quadVertexRect.y(), 0.0f, 0.0f, 0.0f, + quadVertexRect.maxX(), quadVertexRect.y(), 0.0f, 1.0f, 0.0f, + quadVertexRect.maxX(), quadVertexRect.maxY(), 0.0f, 1.0f, 1.0f }; + uint16_t indices[] = { 0, 1, 2, 0, 2, 3, // The two triangles that make up the layer quad. + 0, 1, 2, 3}; // A line path for drawing the layer border. + + GLC(m_context, m_quadVerticesVbo = m_context->createBuffer()); + GLC(m_context, m_quadElementsVbo = m_context->createBuffer()); + GLC(m_context, m_context->bindBuffer(GraphicsContext3D::ARRAY_BUFFER, m_quadVerticesVbo)); + GLC(m_context, m_context->bufferData(GraphicsContext3D::ARRAY_BUFFER, sizeof(vertices), vertices, GraphicsContext3D::STATIC_DRAW)); + GLC(m_context, m_context->bindBuffer(GraphicsContext3D::ELEMENT_ARRAY_BUFFER, m_quadElementsVbo)); + GLC(m_context, m_context->bufferData(GraphicsContext3D::ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GraphicsContext3D::STATIC_DRAW)); + + m_initialized = true; +} + +GeometryBinding::~GeometryBinding() +{ + GLC(m_context, m_context->deleteBuffer(m_quadVerticesVbo)); + GLC(m_context, m_context->deleteBuffer(m_quadElementsVbo)); +} + +void GeometryBinding::prepareForDraw() +{ + GLC(m_context, m_context->bindBuffer(GraphicsContext3D::ARRAY_BUFFER, quadVerticesVbo())); + GLC(m_context, m_context->bindBuffer(GraphicsContext3D::ELEMENT_ARRAY_BUFFER, quadElementsVbo())); + unsigned offset = 0; + GLC(m_context, m_context->vertexAttribPointer(positionAttribLocation(), 3, GraphicsContext3D::FLOAT, false, 5 * sizeof(float), offset)); + offset += 3 * sizeof(float); + GLC(m_context, m_context->vertexAttribPointer(texCoordAttribLocation(), 2, GraphicsContext3D::FLOAT, false, 5 * sizeof(float), offset)); + GLC(m_context, m_context->enableVertexAttribArray(positionAttribLocation())); + GLC(m_context, m_context->enableVertexAttribArray(texCoordAttribLocation())); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/GeometryBinding.h b/cc/GeometryBinding.h new file mode 100644 index 0000000..c018ff7 --- /dev/null +++ b/cc/GeometryBinding.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef GeometryBinding_h +#define GeometryBinding_h + +#include "FloatRect.h" + +#if USE(ACCELERATED_COMPOSITING) + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace WebCore { + +class GeometryBinding { +public: + GeometryBinding(WebKit::WebGraphicsContext3D*, const FloatRect& quadVertexRect); + ~GeometryBinding(); + + bool initialized() const { return m_initialized; } + + WebKit::WebGraphicsContext3D* context() const { return m_context; } + unsigned quadVerticesVbo() const { return m_quadVerticesVbo; } + unsigned quadElementsVbo() const { return m_quadElementsVbo; } + + void prepareForDraw(); + + // All layer shaders share the same attribute locations for the vertex + // positions and texture coordinates. This allows switching shaders without + // rebinding attribute arrays. + static int positionAttribLocation() { return 0; } + static int texCoordAttribLocation() { return 1; } + +private: + WebKit::WebGraphicsContext3D* m_context; + unsigned m_quadVerticesVbo; + unsigned m_quadElementsVbo; + bool m_initialized; +}; + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/HeadsUpDisplayLayerChromium.cpp b/cc/HeadsUpDisplayLayerChromium.cpp new file mode 100644 index 0000000..9a782cd --- /dev/null +++ b/cc/HeadsUpDisplayLayerChromium.cpp @@ -0,0 +1,70 @@ +// Copyright 2012 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 "HeadsUpDisplayLayerChromium.h" + +#include "CCHeadsUpDisplayLayerImpl.h" +#include "CCLayerTreeHost.h" +#include "TraceEvent.h" + +namespace WebCore { + +PassRefPtr<HeadsUpDisplayLayerChromium> HeadsUpDisplayLayerChromium::create() +{ + return adoptRef(new HeadsUpDisplayLayerChromium()); +} + +HeadsUpDisplayLayerChromium::HeadsUpDisplayLayerChromium() + : LayerChromium() +{ + + setBounds(IntSize(512, 128)); +} + +HeadsUpDisplayLayerChromium::~HeadsUpDisplayLayerChromium() +{ +} + +void HeadsUpDisplayLayerChromium::update(CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&) +{ + const CCLayerTreeSettings& settings = layerTreeHost()->settings(); + int maxTextureSize = layerTreeHost()->rendererCapabilities().maxTextureSize; + + IntSize bounds; + if (settings.showPlatformLayerTree || settings.showDebugRects()) { + bounds.setWidth(std::min(maxTextureSize, layerTreeHost()->deviceViewportSize().width())); + bounds.setHeight(std::min(maxTextureSize, layerTreeHost()->deviceViewportSize().height())); + } else { + bounds.setWidth(512); + bounds.setHeight(128); + } + + setBounds(bounds); +} + +void HeadsUpDisplayLayerChromium::setFontAtlas(PassOwnPtr<CCFontAtlas> fontAtlas) +{ + m_fontAtlas = fontAtlas; + setNeedsCommit(); +} + +PassOwnPtr<CCLayerImpl> HeadsUpDisplayLayerChromium::createCCLayerImpl() +{ + return CCHeadsUpDisplayLayerImpl::create(m_layerId); +} + +void HeadsUpDisplayLayerChromium::pushPropertiesTo(CCLayerImpl* layerImpl) +{ + LayerChromium::pushPropertiesTo(layerImpl); + + if (!m_fontAtlas) + return; + + CCHeadsUpDisplayLayerImpl* hudLayerImpl = static_cast<CCHeadsUpDisplayLayerImpl*>(layerImpl); + hudLayerImpl->setFontAtlas(m_fontAtlas.release()); +} + +} diff --git a/cc/HeadsUpDisplayLayerChromium.h b/cc/HeadsUpDisplayLayerChromium.h new file mode 100644 index 0000000..2f81574 --- /dev/null +++ b/cc/HeadsUpDisplayLayerChromium.h @@ -0,0 +1,36 @@ +// Copyright 2012 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. + + +#ifndef HeadsUpDisplayLayerChromium_h +#define HeadsUpDisplayLayerChromium_h + +#include "CCFontAtlas.h" +#include "IntSize.h" +#include "LayerChromium.h" + +namespace WebCore { + +class HeadsUpDisplayLayerChromium : public LayerChromium { +public: + static PassRefPtr<HeadsUpDisplayLayerChromium> create(); + virtual ~HeadsUpDisplayLayerChromium(); + + virtual void update(CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&) OVERRIDE; + virtual bool drawsContent() const OVERRIDE { return true; } + + void setFontAtlas(PassOwnPtr<CCFontAtlas>); + + virtual PassOwnPtr<CCLayerImpl> createCCLayerImpl() OVERRIDE; + virtual void pushPropertiesTo(CCLayerImpl*) OVERRIDE; + +protected: + HeadsUpDisplayLayerChromium(); + +private: + OwnPtr<CCFontAtlas> m_fontAtlas; +}; + +} +#endif diff --git a/cc/IOSurfaceLayerChromium.cpp b/cc/IOSurfaceLayerChromium.cpp new file mode 100644 index 0000000..fde7140 --- /dev/null +++ b/cc/IOSurfaceLayerChromium.cpp @@ -0,0 +1,56 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "IOSurfaceLayerChromium.h" + +#include "CCIOSurfaceLayerImpl.h" + +namespace WebCore { + +PassRefPtr<IOSurfaceLayerChromium> IOSurfaceLayerChromium::create() +{ + return adoptRef(new IOSurfaceLayerChromium()); +} + +IOSurfaceLayerChromium::IOSurfaceLayerChromium() + : LayerChromium() + , m_ioSurfaceId(0) +{ +} + +IOSurfaceLayerChromium::~IOSurfaceLayerChromium() +{ +} + +void IOSurfaceLayerChromium::setIOSurfaceProperties(uint32_t ioSurfaceId, const IntSize& size) +{ + m_ioSurfaceId = ioSurfaceId; + m_ioSurfaceSize = size; + setNeedsCommit(); +} + +PassOwnPtr<CCLayerImpl> IOSurfaceLayerChromium::createCCLayerImpl() +{ + return CCIOSurfaceLayerImpl::create(m_layerId); +} + +bool IOSurfaceLayerChromium::drawsContent() const +{ + return m_ioSurfaceId && LayerChromium::drawsContent(); +} + +void IOSurfaceLayerChromium::pushPropertiesTo(CCLayerImpl* layer) +{ + LayerChromium::pushPropertiesTo(layer); + + CCIOSurfaceLayerImpl* textureLayer = static_cast<CCIOSurfaceLayerImpl*>(layer); + textureLayer->setIOSurfaceProperties(m_ioSurfaceId, m_ioSurfaceSize); +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/IOSurfaceLayerChromium.h b/cc/IOSurfaceLayerChromium.h new file mode 100644 index 0000000..47247f8 --- /dev/null +++ b/cc/IOSurfaceLayerChromium.h @@ -0,0 +1,38 @@ +// Copyright 2012 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. + + +#ifndef IOSurfaceLayerChromium_h +#define IOSurfaceLayerChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "LayerChromium.h" + +namespace WebCore { + +class IOSurfaceLayerChromium : public LayerChromium { +public: + static PassRefPtr<IOSurfaceLayerChromium> create(); + virtual ~IOSurfaceLayerChromium(); + + void setIOSurfaceProperties(uint32_t ioSurfaceId, const IntSize&); + + virtual PassOwnPtr<CCLayerImpl> createCCLayerImpl() OVERRIDE; + virtual bool drawsContent() const OVERRIDE; + virtual void pushPropertiesTo(CCLayerImpl*) OVERRIDE; + +protected: + IOSurfaceLayerChromium(); + +private: + + uint32_t m_ioSurfaceId; + IntSize m_ioSurfaceSize; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/ImageLayerChromium.cpp b/cc/ImageLayerChromium.cpp new file mode 100644 index 0000000..09c97d8 --- /dev/null +++ b/cc/ImageLayerChromium.cpp @@ -0,0 +1,161 @@ +// 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "ImageLayerChromium.h" + +#include "CCLayerTreeHost.h" +#include "LayerTextureUpdater.h" +#include "PlatformColor.h" + +namespace WebCore { + +class ImageLayerTextureUpdater : public LayerTextureUpdater { +public: + class Texture : public LayerTextureUpdater::Texture { + public: + Texture(ImageLayerTextureUpdater* textureUpdater, PassOwnPtr<CCPrioritizedTexture> texture) + : LayerTextureUpdater::Texture(texture) + , m_textureUpdater(textureUpdater) + { + } + + virtual void updateRect(CCResourceProvider* resourceProvider, const IntRect& sourceRect, const IntSize& destOffset) OVERRIDE + { + textureUpdater()->updateTextureRect(resourceProvider, texture(), sourceRect, destOffset); + } + + private: + ImageLayerTextureUpdater* textureUpdater() { return m_textureUpdater; } + + ImageLayerTextureUpdater* m_textureUpdater; + }; + + static PassRefPtr<ImageLayerTextureUpdater> create() + { + return adoptRef(new ImageLayerTextureUpdater()); + } + + virtual ~ImageLayerTextureUpdater() { } + + virtual PassOwnPtr<LayerTextureUpdater::Texture> createTexture(CCPrioritizedTextureManager* manager) + { + return adoptPtr(new Texture(this, CCPrioritizedTexture::create(manager))); + } + + virtual SampledTexelFormat sampledTexelFormat(GC3Denum textureFormat) OVERRIDE + { + return PlatformColor::sameComponentOrder(textureFormat) ? + LayerTextureUpdater::SampledTexelFormatRGBA : LayerTextureUpdater::SampledTexelFormatBGRA; + } + + void updateTextureRect(CCResourceProvider* resourceProvider, CCPrioritizedTexture* texture, const IntRect& sourceRect, const IntSize& destOffset) + { + // Source rect should never go outside the image pixels, even if this + // is requested because the texture extends outside the image. + IntRect clippedSourceRect = sourceRect; + IntRect imageRect = IntRect(0, 0, m_bitmap.width(), m_bitmap.height()); + clippedSourceRect.intersect(imageRect); + + IntSize clippedDestOffset = destOffset + IntSize(clippedSourceRect.location() - sourceRect.location()); + + SkAutoLockPixels lock(m_bitmap); + texture->upload(resourceProvider, static_cast<const uint8_t*>(m_bitmap.getPixels()), imageRect, clippedSourceRect, clippedDestOffset); + } + + void setBitmap(const SkBitmap& bitmap) + { + m_bitmap = bitmap; + } + +private: + ImageLayerTextureUpdater() { } + + SkBitmap m_bitmap; +}; + +PassRefPtr<ImageLayerChromium> ImageLayerChromium::create() +{ + return adoptRef(new ImageLayerChromium()); +} + +ImageLayerChromium::ImageLayerChromium() + : TiledLayerChromium() +{ +} + +ImageLayerChromium::~ImageLayerChromium() +{ +} + +void ImageLayerChromium::setBitmap(const SkBitmap& bitmap) +{ + // setBitmap() currently gets called whenever there is any + // style change that affects the layer even if that change doesn't + // affect the actual contents of the image (e.g. a CSS animation). + // With this check in place we avoid unecessary texture uploads. + if (bitmap.pixelRef() && bitmap.pixelRef() == m_bitmap.pixelRef()) + return; + + m_bitmap = bitmap; + setNeedsDisplay(); +} + +void ImageLayerChromium::setTexturePriorities(const CCPriorityCalculator& priorityCalc) +{ + // Update the tile data before creating all the layer's tiles. + updateTileSizeAndTilingOption(); + + TiledLayerChromium::setTexturePriorities(priorityCalc); +} + +void ImageLayerChromium::update(CCTextureUpdateQueue& queue, const CCOcclusionTracker* occlusion, CCRenderingStats& stats) +{ + createTextureUpdaterIfNeeded(); + if (m_needsDisplay) { + m_textureUpdater->setBitmap(m_bitmap); + updateTileSizeAndTilingOption(); + invalidateContentRect(IntRect(IntPoint(), contentBounds())); + m_needsDisplay = false; + } + TiledLayerChromium::update(queue, occlusion, stats); +} + +void ImageLayerChromium::createTextureUpdaterIfNeeded() +{ + if (m_textureUpdater) + return; + + m_textureUpdater = ImageLayerTextureUpdater::create(); + GC3Denum textureFormat = layerTreeHost()->rendererCapabilities().bestTextureFormat; + setTextureFormat(textureFormat); + setSampledTexelFormat(textureUpdater()->sampledTexelFormat(textureFormat)); +} + +LayerTextureUpdater* ImageLayerChromium::textureUpdater() const +{ + return m_textureUpdater.get(); +} + +IntSize ImageLayerChromium::contentBounds() const +{ + return IntSize(m_bitmap.width(), m_bitmap.height()); +} + +bool ImageLayerChromium::drawsContent() const +{ + return !m_bitmap.isNull() && TiledLayerChromium::drawsContent(); +} + +bool ImageLayerChromium::needsContentsScale() const +{ + // Contents scale is not need for image layer because this can be done in compositor more efficiently. + return false; +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/ImageLayerChromium.h b/cc/ImageLayerChromium.h new file mode 100644 index 0000000..8be33a7 --- /dev/null +++ b/cc/ImageLayerChromium.h @@ -0,0 +1,48 @@ +// 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. + + +#ifndef ImageLayerChromium_h +#define ImageLayerChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "ContentLayerChromium.h" +#include "SkBitmap.h" + +namespace WebCore { + +class ImageLayerTextureUpdater; + +// A Layer that contains only an Image element. +class ImageLayerChromium : public TiledLayerChromium { +public: + static PassRefPtr<ImageLayerChromium> create(); + virtual ~ImageLayerChromium(); + + virtual bool drawsContent() const OVERRIDE; + virtual void setTexturePriorities(const CCPriorityCalculator&) OVERRIDE; + virtual void update(CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&) OVERRIDE; + virtual bool needsContentsScale() const OVERRIDE; + + void setBitmap(const SkBitmap& image); + +private: + ImageLayerChromium(); + + void setTilingOption(TilingOption); + + virtual LayerTextureUpdater* textureUpdater() const OVERRIDE; + virtual void createTextureUpdaterIfNeeded() OVERRIDE; + virtual IntSize contentBounds() const OVERRIDE; + + SkBitmap m_bitmap; + + RefPtr<ImageLayerTextureUpdater> m_textureUpdater; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/LayerChromium.cpp b/cc/LayerChromium.cpp new file mode 100644 index 0000000..8dc15ad --- /dev/null +++ b/cc/LayerChromium.cpp @@ -0,0 +1,717 @@ +// 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" + +#if USE(ACCELERATED_COMPOSITING) +#include "LayerChromium.h" + +#include "CCActiveAnimation.h" +#include "CCAnimationEvents.h" +#include "CCLayerAnimationController.h" +#include "CCLayerImpl.h" +#include "CCLayerTreeHost.h" +#include "CCSettings.h" +#include "TextStream.h" + +#include <public/WebAnimationDelegate.h> + +using namespace std; +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +static int s_nextLayerId = 1; + +PassRefPtr<LayerChromium> LayerChromium::create() +{ + return adoptRef(new LayerChromium()); +} + +LayerChromium::LayerChromium() + : m_needsDisplay(false) + , m_stackingOrderChanged(false) + , m_layerId(s_nextLayerId++) + , m_parent(0) + , m_layerTreeHost(0) + , m_layerAnimationController(CCLayerAnimationController::create(this)) + , m_scrollable(false) + , m_shouldScrollOnMainThread(false) + , m_haveWheelEventHandlers(false) + , m_nonFastScrollableRegionChanged(false) + , m_anchorPoint(0.5, 0.5) + , m_backgroundColor(0) + , m_debugBorderColor(0) + , m_debugBorderWidth(0) + , m_opacity(1.0) + , m_anchorPointZ(0) + , m_isContainerForFixedPositionLayers(false) + , m_fixedToContainerLayer(false) + , m_isDrawable(false) + , m_masksToBounds(false) + , m_opaque(false) + , m_doubleSided(true) + , m_useLCDText(false) + , m_preserves3D(false) + , m_useParentBackfaceVisibility(false) + , m_drawCheckerboardForMissingTiles(false) + , m_forceRenderSurface(false) + , m_replicaLayer(0) + , m_drawOpacity(0) + , m_drawOpacityIsAnimating(false) + , m_renderTarget(0) + , m_drawTransformIsAnimating(false) + , m_screenSpaceTransformIsAnimating(false) + , m_contentsScale(1.0) + , m_layerAnimationDelegate(0) + , m_layerScrollDelegate(0) +{ + if (m_layerId < 0) { + s_nextLayerId = 1; + m_layerId = s_nextLayerId++; + } +} + +LayerChromium::~LayerChromium() +{ + // Our parent should be holding a reference to us so there should be no + // way for us to be destroyed while we still have a parent. + ASSERT(!parent()); + + // Remove the parent reference from all children. + removeAllChildren(); +} + +void LayerChromium::setUseLCDText(bool useLCDText) +{ + m_useLCDText = useLCDText; +} + +void LayerChromium::setLayerTreeHost(CCLayerTreeHost* host) +{ + if (m_layerTreeHost == host) + return; + + m_layerTreeHost = host; + + for (size_t i = 0; i < m_children.size(); ++i) + m_children[i]->setLayerTreeHost(host); + + if (m_maskLayer) + m_maskLayer->setLayerTreeHost(host); + if (m_replicaLayer) + m_replicaLayer->setLayerTreeHost(host); + + // If this layer already has active animations, the host needs to be notified. + if (host && m_layerAnimationController->hasActiveAnimation()) + host->didAddAnimation(); +} + +void LayerChromium::setNeedsCommit() +{ + if (m_layerTreeHost) + m_layerTreeHost->setNeedsCommit(); +} + +void LayerChromium::setParent(LayerChromium* layer) +{ + ASSERT(!layer || !layer->hasAncestor(this)); + m_parent = layer; + setLayerTreeHost(m_parent ? m_parent->layerTreeHost() : 0); +} + +bool LayerChromium::hasAncestor(LayerChromium* ancestor) const +{ + for (LayerChromium* layer = parent(); layer; layer = layer->parent()) { + if (layer == ancestor) + return true; + } + return false; +} + +void LayerChromium::addChild(PassRefPtr<LayerChromium> child) +{ + insertChild(child, numChildren()); +} + +void LayerChromium::insertChild(PassRefPtr<LayerChromium> child, size_t index) +{ + index = min(index, m_children.size()); + child->removeFromParent(); + child->setParent(this); + child->m_stackingOrderChanged = true; + m_children.insert(index, child); + setNeedsCommit(); +} + +void LayerChromium::removeFromParent() +{ + if (m_parent) + m_parent->removeChild(this); +} + +void LayerChromium::removeChild(LayerChromium* child) +{ + int foundIndex = indexOfChild(child); + if (foundIndex == -1) + return; + + child->setParent(0); + m_children.remove(foundIndex); + setNeedsCommit(); +} + +void LayerChromium::replaceChild(LayerChromium* reference, PassRefPtr<LayerChromium> newLayer) +{ + ASSERT_ARG(reference, reference); + ASSERT_ARG(reference, reference->parent() == this); + + if (reference == newLayer) + return; + + int referenceIndex = indexOfChild(reference); + if (referenceIndex == -1) { + ASSERT_NOT_REACHED(); + return; + } + + reference->removeFromParent(); + + if (newLayer) { + newLayer->removeFromParent(); + insertChild(newLayer, referenceIndex); + } +} + +int LayerChromium::indexOfChild(const LayerChromium* reference) +{ + for (size_t i = 0; i < m_children.size(); i++) { + if (m_children[i] == reference) + return i; + } + return -1; +} + +void LayerChromium::setBounds(const IntSize& size) +{ + if (bounds() == size) + return; + + bool firstResize = bounds().isEmpty() && !size.isEmpty(); + + m_bounds = size; + + if (firstResize) + setNeedsDisplay(); + else + setNeedsCommit(); +} + +LayerChromium* LayerChromium::rootLayer() +{ + LayerChromium* layer = this; + while (layer->parent()) + layer = layer->parent(); + return layer; +} + +void LayerChromium::removeAllChildren() +{ + while (m_children.size()) { + LayerChromium* layer = m_children[0].get(); + ASSERT(layer->parent()); + layer->removeFromParent(); + } +} + +void LayerChromium::setChildren(const Vector<RefPtr<LayerChromium> >& children) +{ + if (children == m_children) + return; + + removeAllChildren(); + size_t listSize = children.size(); + for (size_t i = 0; i < listSize; i++) + addChild(children[i]); +} + +void LayerChromium::setAnchorPoint(const FloatPoint& anchorPoint) +{ + if (m_anchorPoint == anchorPoint) + return; + m_anchorPoint = anchorPoint; + setNeedsCommit(); +} + +void LayerChromium::setAnchorPointZ(float anchorPointZ) +{ + if (m_anchorPointZ == anchorPointZ) + return; + m_anchorPointZ = anchorPointZ; + setNeedsCommit(); +} + +void LayerChromium::setBackgroundColor(SkColor backgroundColor) +{ + if (m_backgroundColor == backgroundColor) + return; + m_backgroundColor = backgroundColor; + setNeedsCommit(); +} + +void LayerChromium::setMasksToBounds(bool masksToBounds) +{ + if (m_masksToBounds == masksToBounds) + return; + m_masksToBounds = masksToBounds; + setNeedsCommit(); +} + +void LayerChromium::setMaskLayer(LayerChromium* maskLayer) +{ + if (m_maskLayer == maskLayer) + return; + if (m_maskLayer) + m_maskLayer->setLayerTreeHost(0); + m_maskLayer = maskLayer; + if (m_maskLayer) { + m_maskLayer->setLayerTreeHost(m_layerTreeHost); + m_maskLayer->setIsMask(true); + } + setNeedsCommit(); +} + +void LayerChromium::setReplicaLayer(LayerChromium* layer) +{ + if (m_replicaLayer == layer) + return; + if (m_replicaLayer) + m_replicaLayer->setLayerTreeHost(0); + m_replicaLayer = layer; + if (m_replicaLayer) + m_replicaLayer->setLayerTreeHost(m_layerTreeHost); + setNeedsCommit(); +} + +void LayerChromium::setFilters(const WebKit::WebFilterOperations& filters) +{ + if (m_filters == filters) + return; + m_filters = filters; + setNeedsCommit(); + if (!filters.isEmpty()) + CCLayerTreeHost::setNeedsFilterContext(true); +} + +void LayerChromium::setBackgroundFilters(const WebKit::WebFilterOperations& backgroundFilters) +{ + if (m_backgroundFilters == backgroundFilters) + return; + m_backgroundFilters = backgroundFilters; + setNeedsCommit(); + if (!backgroundFilters.isEmpty()) + CCLayerTreeHost::setNeedsFilterContext(true); +} + +void LayerChromium::setOpacity(float opacity) +{ + if (m_opacity == opacity) + return; + m_opacity = opacity; + setNeedsCommit(); +} + +bool LayerChromium::opacityIsAnimating() const +{ + return m_layerAnimationController->isAnimatingProperty(CCActiveAnimation::Opacity); +} + +void LayerChromium::setOpaque(bool opaque) +{ + if (m_opaque == opaque) + return; + m_opaque = opaque; + setNeedsDisplay(); +} + +void LayerChromium::setPosition(const FloatPoint& position) +{ + if (m_position == position) + return; + m_position = position; + setNeedsCommit(); +} + +void LayerChromium::setSublayerTransform(const WebTransformationMatrix& sublayerTransform) +{ + if (m_sublayerTransform == sublayerTransform) + return; + m_sublayerTransform = sublayerTransform; + setNeedsCommit(); +} + +void LayerChromium::setTransform(const WebTransformationMatrix& transform) +{ + if (m_transform == transform) + return; + m_transform = transform; + setNeedsCommit(); +} + +bool LayerChromium::transformIsAnimating() const +{ + return m_layerAnimationController->isAnimatingProperty(CCActiveAnimation::Transform); +} + +void LayerChromium::setScrollPosition(const IntPoint& scrollPosition) +{ + if (m_scrollPosition == scrollPosition) + return; + m_scrollPosition = scrollPosition; + setNeedsCommit(); +} + +void LayerChromium::setMaxScrollPosition(const IntSize& maxScrollPosition) +{ + if (m_maxScrollPosition == maxScrollPosition) + return; + m_maxScrollPosition = maxScrollPosition; + setNeedsCommit(); +} + +void LayerChromium::setScrollable(bool scrollable) +{ + if (m_scrollable == scrollable) + return; + m_scrollable = scrollable; + setNeedsCommit(); +} + +void LayerChromium::setShouldScrollOnMainThread(bool shouldScrollOnMainThread) +{ + if (m_shouldScrollOnMainThread == shouldScrollOnMainThread) + return; + m_shouldScrollOnMainThread = shouldScrollOnMainThread; + setNeedsCommit(); +} + +void LayerChromium::setHaveWheelEventHandlers(bool haveWheelEventHandlers) +{ + if (m_haveWheelEventHandlers == haveWheelEventHandlers) + return; + m_haveWheelEventHandlers = haveWheelEventHandlers; + setNeedsCommit(); +} + +void LayerChromium::setNonFastScrollableRegion(const Region& region) +{ + if (m_nonFastScrollableRegion == region) + return; + m_nonFastScrollableRegion = region; + m_nonFastScrollableRegionChanged = true; + setNeedsCommit(); +} + +void LayerChromium::scrollBy(const IntSize& scrollDelta) +{ + setScrollPosition(scrollPosition() + scrollDelta); + if (m_layerScrollDelegate) + m_layerScrollDelegate->didScroll(scrollDelta); +} + +void LayerChromium::setDrawCheckerboardForMissingTiles(bool checkerboard) +{ + if (m_drawCheckerboardForMissingTiles == checkerboard) + return; + m_drawCheckerboardForMissingTiles = checkerboard; + setNeedsCommit(); +} + +void LayerChromium::setForceRenderSurface(bool force) +{ + if (m_forceRenderSurface == force) + return; + m_forceRenderSurface = force; + setNeedsCommit(); +} + +void LayerChromium::setDoubleSided(bool doubleSided) +{ + if (m_doubleSided == doubleSided) + return; + m_doubleSided = doubleSided; + setNeedsCommit(); +} + +void LayerChromium::setIsDrawable(bool isDrawable) +{ + if (m_isDrawable == isDrawable) + return; + + m_isDrawable = isDrawable; + setNeedsCommit(); +} + +LayerChromium* LayerChromium::parent() const +{ + return m_parent; +} + +void LayerChromium::setNeedsDisplayRect(const FloatRect& dirtyRect) +{ + m_updateRect.unite(dirtyRect); + + // Simply mark the contents as dirty. For non-root layers, the call to + // setNeedsCommit will schedule a fresh compositing pass. + // For the root layer, setNeedsCommit has no effect. + if (!dirtyRect.isEmpty()) + m_needsDisplay = true; + + setNeedsCommit(); +} + +bool LayerChromium::descendantIsFixedToContainerLayer() const +{ + for (size_t i = 0; i < m_children.size(); ++i) { + if (m_children[i]->fixedToContainerLayer() || m_children[i]->descendantIsFixedToContainerLayer()) + return true; + } + return false; +} + +void LayerChromium::setIsContainerForFixedPositionLayers(bool isContainerForFixedPositionLayers) +{ + if (m_isContainerForFixedPositionLayers == isContainerForFixedPositionLayers) + return; + m_isContainerForFixedPositionLayers = isContainerForFixedPositionLayers; + + if (m_layerTreeHost && m_layerTreeHost->commitRequested()) + return; + + // Only request a commit if we have a fixed positioned descendant. + if (descendantIsFixedToContainerLayer()) + setNeedsCommit(); +} + +void LayerChromium::setFixedToContainerLayer(bool fixedToContainerLayer) +{ + if (m_fixedToContainerLayer == fixedToContainerLayer) + return; + m_fixedToContainerLayer = fixedToContainerLayer; + setNeedsCommit(); +} + +void LayerChromium::pushPropertiesTo(CCLayerImpl* layer) +{ + layer->setAnchorPoint(m_anchorPoint); + layer->setAnchorPointZ(m_anchorPointZ); + layer->setBackgroundColor(m_backgroundColor); + layer->setBounds(m_bounds); + layer->setContentBounds(contentBounds()); + layer->setDebugBorderColor(m_debugBorderColor); + layer->setDebugBorderWidth(m_debugBorderWidth); + layer->setDebugName(m_debugName.isolatedCopy()); // We have to use isolatedCopy() here to safely pass ownership to another thread. + layer->setDoubleSided(m_doubleSided); + layer->setDrawCheckerboardForMissingTiles(m_drawCheckerboardForMissingTiles); + layer->setForceRenderSurface(m_forceRenderSurface); + layer->setDrawsContent(drawsContent()); + layer->setFilters(filters()); + layer->setBackgroundFilters(backgroundFilters()); + layer->setUseLCDText(m_useLCDText); + layer->setMasksToBounds(m_masksToBounds); + layer->setScrollable(m_scrollable); + layer->setShouldScrollOnMainThread(m_shouldScrollOnMainThread); + layer->setHaveWheelEventHandlers(m_haveWheelEventHandlers); + // Copying a Region is more expensive than most layer properties, since it involves copying two Vectors that may be + // arbitrarily large depending on page content, so we only push the property if it's changed. + if (m_nonFastScrollableRegionChanged) { + layer->setNonFastScrollableRegion(m_nonFastScrollableRegion); + m_nonFastScrollableRegionChanged = false; + } + layer->setOpaque(m_opaque); + if (!opacityIsAnimating()) + layer->setOpacity(m_opacity); + layer->setPosition(m_position); + layer->setIsContainerForFixedPositionLayers(m_isContainerForFixedPositionLayers); + layer->setFixedToContainerLayer(m_fixedToContainerLayer); + layer->setPreserves3D(preserves3D()); + layer->setUseParentBackfaceVisibility(m_useParentBackfaceVisibility); + layer->setScrollPosition(m_scrollPosition); + layer->setMaxScrollPosition(m_maxScrollPosition); + layer->setSublayerTransform(m_sublayerTransform); + if (!transformIsAnimating()) + layer->setTransform(m_transform); + + // If the main thread commits multiple times before the impl thread actually draws, then damage tracking + // will become incorrect if we simply clobber the updateRect here. The CCLayerImpl's updateRect needs to + // accumulate (i.e. union) any update changes that have occurred on the main thread. + m_updateRect.uniteIfNonZero(layer->updateRect()); + layer->setUpdateRect(m_updateRect); + + layer->setScrollDelta(layer->scrollDelta() - layer->sentScrollDelta()); + layer->setSentScrollDelta(IntSize()); + + layer->setStackingOrderChanged(m_stackingOrderChanged); + + if (maskLayer()) + maskLayer()->pushPropertiesTo(layer->maskLayer()); + if (replicaLayer()) + replicaLayer()->pushPropertiesTo(layer->replicaLayer()); + + m_layerAnimationController->pushAnimationUpdatesTo(layer->layerAnimationController()); + + // Reset any state that should be cleared for the next update. + m_stackingOrderChanged = false; + m_updateRect = FloatRect(); +} + +PassOwnPtr<CCLayerImpl> LayerChromium::createCCLayerImpl() +{ + return CCLayerImpl::create(m_layerId); +} + +void LayerChromium::setDebugBorderColor(SkColor color) +{ + m_debugBorderColor = color; + setNeedsCommit(); +} + +void LayerChromium::setDebugBorderWidth(float width) +{ + m_debugBorderWidth = width; + setNeedsCommit(); +} + +void LayerChromium::setDebugName(const String& debugName) +{ + m_debugName = debugName; + setNeedsCommit(); +} + +void LayerChromium::setContentsScale(float contentsScale) +{ + if (!needsContentsScale() || m_contentsScale == contentsScale) + return; + m_contentsScale = contentsScale; + setNeedsDisplay(); +} + +void LayerChromium::createRenderSurface() +{ + ASSERT(!m_renderSurface); + m_renderSurface = adoptPtr(new RenderSurfaceChromium(this)); + setRenderTarget(this); +} + +bool LayerChromium::descendantDrawsContent() +{ + for (size_t i = 0; i < m_children.size(); ++i) { + if (m_children[i]->drawsContent() || m_children[i]->descendantDrawsContent()) + return true; + } + return false; +} + +void LayerChromium::setOpacityFromAnimation(float opacity) +{ + // This is called due to an ongoing accelerated animation. Since this animation is + // also being run on the impl thread, there is no need to request a commit to push + // this value over, so set the value directly rather than calling setOpacity. + m_opacity = opacity; +} + +void LayerChromium::setTransformFromAnimation(const WebTransformationMatrix& transform) +{ + // This is called due to an ongoing accelerated animation. Since this animation is + // also being run on the impl thread, there is no need to request a commit to push + // this value over, so set this value directly rather than calling setTransform. + m_transform = transform; +} + +bool LayerChromium::addAnimation(PassOwnPtr<CCActiveAnimation> animation) +{ + if (!CCSettings::acceleratedAnimationEnabled()) + return false; + + m_layerAnimationController->addAnimation(animation); + if (m_layerTreeHost) { + m_layerTreeHost->didAddAnimation(); + setNeedsCommit(); + } + return true; +} + +void LayerChromium::pauseAnimation(int animationId, double timeOffset) +{ + m_layerAnimationController->pauseAnimation(animationId, timeOffset); + setNeedsCommit(); +} + +void LayerChromium::removeAnimation(int animationId) +{ + m_layerAnimationController->removeAnimation(animationId); + setNeedsCommit(); +} + +void LayerChromium::suspendAnimations(double monotonicTime) +{ + m_layerAnimationController->suspendAnimations(monotonicTime); + setNeedsCommit(); +} + +void LayerChromium::resumeAnimations(double monotonicTime) +{ + m_layerAnimationController->resumeAnimations(monotonicTime); + setNeedsCommit(); +} + +void LayerChromium::setLayerAnimationController(PassOwnPtr<CCLayerAnimationController> layerAnimationController) +{ + m_layerAnimationController = layerAnimationController; + if (m_layerAnimationController) { + m_layerAnimationController->setClient(this); + m_layerAnimationController->setForceSync(); + } + setNeedsCommit(); +} + +PassOwnPtr<CCLayerAnimationController> LayerChromium::releaseLayerAnimationController() +{ + OwnPtr<CCLayerAnimationController> toReturn = m_layerAnimationController.release(); + m_layerAnimationController = CCLayerAnimationController::create(this); + return toReturn.release(); +} + +bool LayerChromium::hasActiveAnimation() const +{ + return m_layerAnimationController->hasActiveAnimation(); +} + +void LayerChromium::notifyAnimationStarted(const CCAnimationEvent& event, double wallClockTime) +{ + m_layerAnimationController->notifyAnimationStarted(event); + if (m_layerAnimationDelegate) + m_layerAnimationDelegate->notifyAnimationStarted(wallClockTime); +} + +void LayerChromium::notifyAnimationFinished(double wallClockTime) +{ + if (m_layerAnimationDelegate) + m_layerAnimationDelegate->notifyAnimationFinished(wallClockTime); +} + +Region LayerChromium::visibleContentOpaqueRegion() const +{ + if (opaque()) + return visibleContentRect(); + return Region(); +} + +void sortLayers(Vector<RefPtr<LayerChromium> >::iterator, Vector<RefPtr<LayerChromium> >::iterator, void*) +{ + // Currently we don't use z-order to decide what to paint, so there's no need to actually sort LayerChromiums. +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/LayerChromium.h b/cc/LayerChromium.h new file mode 100644 index 0000000..273bf8e --- /dev/null +++ b/cc/LayerChromium.h @@ -0,0 +1,381 @@ +// 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. + + +#ifndef LayerChromium_h +#define LayerChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCLayerAnimationController.h" +#include "CCOcclusionTracker.h" +#include "CCPrioritizedTexture.h" +#include "FloatPoint.h" +#include "Region.h" +#include "RenderSurfaceChromium.h" +#include "SkColor.h" + +#include <public/WebFilterOperations.h> +#include <public/WebTransformationMatrix.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> +#include <wtf/text/StringHash.h> +#include <wtf/text/WTFString.h> + +namespace WebKit { +class WebAnimationDelegate; +} + +namespace WebCore { + +class CCActiveAnimation; +struct CCAnimationEvent; +class CCLayerAnimationDelegate; +class CCLayerImpl; +class CCLayerTreeHost; +class CCTextureUpdateQueue; +class ScrollbarLayerChromium; +struct CCAnimationEvent; +struct CCRenderingStats; + +// Delegate for handling scroll input for a LayerChromium. +class LayerChromiumScrollDelegate { +public: + virtual void didScroll(const IntSize&) = 0; + +protected: + virtual ~LayerChromiumScrollDelegate() { } +}; + +// Base class for composited layers. Special layer types are derived from +// this class. +class LayerChromium : public RefCounted<LayerChromium>, public CCLayerAnimationControllerClient { +public: + static PassRefPtr<LayerChromium> create(); + + virtual ~LayerChromium(); + + // CCLayerAnimationControllerClient implementation + virtual int id() const OVERRIDE { return m_layerId; } + virtual void setOpacityFromAnimation(float) OVERRIDE; + virtual float opacity() const OVERRIDE { return m_opacity; } + virtual void setTransformFromAnimation(const WebKit::WebTransformationMatrix&) OVERRIDE; + // A layer's transform operates layer space. That is, entirely in logical, + // non-page-scaled pixels (that is, they have page zoom baked in, but not page scale). + // The root layer is a special case -- it operates in physical pixels. + virtual const WebKit::WebTransformationMatrix& transform() const OVERRIDE { return m_transform; } + + LayerChromium* rootLayer(); + LayerChromium* parent() const; + void addChild(PassRefPtr<LayerChromium>); + void insertChild(PassRefPtr<LayerChromium>, size_t index); + void replaceChild(LayerChromium* reference, PassRefPtr<LayerChromium> newLayer); + void removeFromParent(); + void removeAllChildren(); + void setChildren(const Vector<RefPtr<LayerChromium> >&); + const Vector<RefPtr<LayerChromium> >& children() const { return m_children; } + + void setAnchorPoint(const FloatPoint&); + FloatPoint anchorPoint() const { return m_anchorPoint; } + + void setAnchorPointZ(float); + float anchorPointZ() const { return m_anchorPointZ; } + + void setBackgroundColor(SkColor); + SkColor backgroundColor() const { return m_backgroundColor; } + + // A layer's bounds are in logical, non-page-scaled pixels (however, the + // root layer's bounds are in physical pixels). + void setBounds(const IntSize&); + const IntSize& bounds() const { return m_bounds; } + virtual IntSize contentBounds() const { return bounds(); } + + void setMasksToBounds(bool); + bool masksToBounds() const { return m_masksToBounds; } + + void setMaskLayer(LayerChromium*); + LayerChromium* maskLayer() const { return m_maskLayer.get(); } + + virtual void setNeedsDisplayRect(const FloatRect& dirtyRect); + void setNeedsDisplay() { setNeedsDisplayRect(FloatRect(FloatPoint(), bounds())); } + virtual bool needsDisplay() const { return m_needsDisplay; } + + void setOpacity(float); + bool opacityIsAnimating() const; + + void setFilters(const WebKit::WebFilterOperations&); + const WebKit::WebFilterOperations& filters() const { return m_filters; } + + // Background filters are filters applied to what is behind this layer, when they are viewed through non-opaque + // regions in this layer. They are used through the WebLayer interface, and are not exposed to HTML. + void setBackgroundFilters(const WebKit::WebFilterOperations&); + const WebKit::WebFilterOperations& backgroundFilters() const { return m_backgroundFilters; } + + virtual void setOpaque(bool); + bool opaque() const { return m_opaque; } + + void setPosition(const FloatPoint&); + FloatPoint position() const { return m_position; } + + void setIsContainerForFixedPositionLayers(bool); + bool isContainerForFixedPositionLayers() const { return m_isContainerForFixedPositionLayers; } + + void setFixedToContainerLayer(bool); + bool fixedToContainerLayer() const { return m_fixedToContainerLayer; } + + void setSublayerTransform(const WebKit::WebTransformationMatrix&); + const WebKit::WebTransformationMatrix& sublayerTransform() const { return m_sublayerTransform; } + + void setTransform(const WebKit::WebTransformationMatrix&); + bool transformIsAnimating() const; + + const IntRect& visibleContentRect() const { return m_visibleContentRect; } + void setVisibleContentRect(const IntRect& visibleContentRect) { m_visibleContentRect = visibleContentRect; } + + void setScrollPosition(const IntPoint&); + const IntPoint& scrollPosition() const { return m_scrollPosition; } + + void setMaxScrollPosition(const IntSize&); + const IntSize& maxScrollPosition() const { return m_maxScrollPosition; } + + void setScrollable(bool); + bool scrollable() const { return m_scrollable; } + void setShouldScrollOnMainThread(bool); + void setHaveWheelEventHandlers(bool); + const Region& nonFastScrollableRegion() { return m_nonFastScrollableRegion; } + void setNonFastScrollableRegion(const Region&); + void setNonFastScrollableRegionChanged() { m_nonFastScrollableRegionChanged = true; } + void setLayerScrollDelegate(LayerChromiumScrollDelegate* layerScrollDelegate) { m_layerScrollDelegate = layerScrollDelegate; } + void scrollBy(const IntSize&); + + void setDrawCheckerboardForMissingTiles(bool); + bool drawCheckerboardForMissingTiles() const { return m_drawCheckerboardForMissingTiles; } + + bool forceRenderSurface() const { return m_forceRenderSurface; } + void setForceRenderSurface(bool); + + IntSize scrollDelta() const { return IntSize(); } + + float pageScaleDelta() const { return 1; } + + void setDoubleSided(bool); + bool doubleSided() const { return m_doubleSided; } + + void setPreserves3D(bool preserve3D) { m_preserves3D = preserve3D; } + bool preserves3D() const { return m_preserves3D; } + + void setUseParentBackfaceVisibility(bool useParentBackfaceVisibility) { m_useParentBackfaceVisibility = useParentBackfaceVisibility; } + bool useParentBackfaceVisibility() const { return m_useParentBackfaceVisibility; } + + virtual void setUseLCDText(bool); + bool useLCDText() const { return m_useLCDText; } + + virtual void setLayerTreeHost(CCLayerTreeHost*); + + void setIsDrawable(bool); + + void setReplicaLayer(LayerChromium*); + LayerChromium* replicaLayer() const { return m_replicaLayer.get(); } + + bool hasMask() const { return m_maskLayer; } + bool hasReplica() const { return m_replicaLayer; } + bool replicaHasMask() const { return m_replicaLayer && (m_maskLayer || m_replicaLayer->m_maskLayer); } + + // These methods typically need to be overwritten by derived classes. + virtual bool drawsContent() const { return m_isDrawable; } + virtual void update(CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&) { } + virtual bool needMoreUpdates() { return false; } + virtual void setIsMask(bool) { } + virtual void bindContentsTexture() { } + virtual bool needsContentsScale() const { return false; } + + void setDebugBorderColor(SkColor); + void setDebugBorderWidth(float); + void setDebugName(const String&); + + virtual void pushPropertiesTo(CCLayerImpl*); + + void clearRenderSurface() { m_renderSurface.clear(); } + RenderSurfaceChromium* renderSurface() const { return m_renderSurface.get(); } + void createRenderSurface(); + + float drawOpacity() const { return m_drawOpacity; } + void setDrawOpacity(float opacity) { m_drawOpacity = opacity; } + + bool drawOpacityIsAnimating() const { return m_drawOpacityIsAnimating; } + void setDrawOpacityIsAnimating(bool drawOpacityIsAnimating) { m_drawOpacityIsAnimating = drawOpacityIsAnimating; } + + LayerChromium* renderTarget() const { ASSERT(!m_renderTarget || m_renderTarget->renderSurface()); return m_renderTarget; } + void setRenderTarget(LayerChromium* target) { m_renderTarget = target; } + + bool drawTransformIsAnimating() const { return m_drawTransformIsAnimating; } + void setDrawTransformIsAnimating(bool animating) { m_drawTransformIsAnimating = animating; } + bool screenSpaceTransformIsAnimating() const { return m_screenSpaceTransformIsAnimating; } + void setScreenSpaceTransformIsAnimating(bool animating) { m_screenSpaceTransformIsAnimating = animating; } + + // This moves from layer space, with origin in the center to target space with origin in the top left. + // That is, it converts from logical, non-page-scaled, to target pixels (and if the target is the + // root render surface, then this converts to physical pixels). + const WebKit::WebTransformationMatrix& drawTransform() const { return m_drawTransform; } + void setDrawTransform(const WebKit::WebTransformationMatrix& matrix) { m_drawTransform = matrix; } + // This moves from content space, with origin the top left to screen space with origin in the top left. + // It converts logical, non-page-scaled pixels to physical pixels. + const WebKit::WebTransformationMatrix& screenSpaceTransform() const { return m_screenSpaceTransform; } + void setScreenSpaceTransform(const WebKit::WebTransformationMatrix& matrix) { m_screenSpaceTransform = matrix; } + const IntRect& drawableContentRect() const { return m_drawableContentRect; } + void setDrawableContentRect(const IntRect& rect) { m_drawableContentRect = rect; } + // The contentsScale converts from logical, non-page-scaled pixels to target pixels. + // The contentsScale is 1 for the root layer as it is already in physical pixels. + float contentsScale() const { return m_contentsScale; } + void setContentsScale(float); + + // Returns true if any of the layer's descendants has content to draw. + bool descendantDrawsContent(); + + CCLayerTreeHost* layerTreeHost() const { return m_layerTreeHost; } + + // Set the priority of all desired textures in this layer. + virtual void setTexturePriorities(const CCPriorityCalculator&) { } + + bool addAnimation(PassOwnPtr<CCActiveAnimation>); + void pauseAnimation(int animationId, double timeOffset); + void removeAnimation(int animationId); + + void suspendAnimations(double monotonicTime); + void resumeAnimations(double monotonicTime); + + CCLayerAnimationController* layerAnimationController() { return m_layerAnimationController.get(); } + void setLayerAnimationController(PassOwnPtr<CCLayerAnimationController>); + PassOwnPtr<CCLayerAnimationController> releaseLayerAnimationController(); + + void setLayerAnimationDelegate(WebKit::WebAnimationDelegate* layerAnimationDelegate) { m_layerAnimationDelegate = layerAnimationDelegate; } + + bool hasActiveAnimation() const; + + virtual void notifyAnimationStarted(const CCAnimationEvent&, double wallClockTime); + virtual void notifyAnimationFinished(double wallClockTime); + + virtual Region visibleContentOpaqueRegion() const; + + virtual ScrollbarLayerChromium* toScrollbarLayerChromium() { return 0; } + +protected: + friend class CCLayerImpl; + friend class TreeSynchronizer; + + LayerChromium(); + + void setNeedsCommit(); + + // This flag is set when layer need repainting/updating. + bool m_needsDisplay; + + // Tracks whether this layer may have changed stacking order with its siblings. + bool m_stackingOrderChanged; + + // The update rect is the region of the compositor resource that was actually updated by the compositor. + // For layers that may do updating outside the compositor's control (i.e. plugin layers), this information + // is not available and the update rect will remain empty. + // Note this rect is in layer space (not content space). + FloatRect m_updateRect; + + RefPtr<LayerChromium> m_maskLayer; + + // Constructs a CCLayerImpl of the correct runtime type for this LayerChromium type. + virtual PassOwnPtr<CCLayerImpl> createCCLayerImpl(); + int m_layerId; + +private: + void setParent(LayerChromium*); + bool hasAncestor(LayerChromium*) const; + bool descendantIsFixedToContainerLayer() const; + + size_t numChildren() const { return m_children.size(); } + + // Returns the index of the child or -1 if not found. + int indexOfChild(const LayerChromium*); + + // This should only be called from removeFromParent. + void removeChild(LayerChromium*); + + Vector<RefPtr<LayerChromium> > m_children; + LayerChromium* m_parent; + + // LayerChromium instances have a weak pointer to their CCLayerTreeHost. + // This pointer value is nil when a LayerChromium is not in a tree and is + // updated via setLayerTreeHost() if a layer moves between trees. + CCLayerTreeHost* m_layerTreeHost; + + OwnPtr<CCLayerAnimationController> m_layerAnimationController; + + // Layer properties. + IntSize m_bounds; + + // Uses layer's content space. + IntRect m_visibleContentRect; + + IntPoint m_scrollPosition; + IntSize m_maxScrollPosition; + bool m_scrollable; + bool m_shouldScrollOnMainThread; + bool m_haveWheelEventHandlers; + Region m_nonFastScrollableRegion; + bool m_nonFastScrollableRegionChanged; + FloatPoint m_position; + FloatPoint m_anchorPoint; + SkColor m_backgroundColor; + SkColor m_debugBorderColor; + float m_debugBorderWidth; + String m_debugName; + float m_opacity; + WebKit::WebFilterOperations m_filters; + WebKit::WebFilterOperations m_backgroundFilters; + float m_anchorPointZ; + bool m_isContainerForFixedPositionLayers; + bool m_fixedToContainerLayer; + bool m_isDrawable; + bool m_masksToBounds; + bool m_opaque; + bool m_doubleSided; + bool m_useLCDText; + bool m_preserves3D; + bool m_useParentBackfaceVisibility; + bool m_drawCheckerboardForMissingTiles; + bool m_forceRenderSurface; + + WebKit::WebTransformationMatrix m_transform; + WebKit::WebTransformationMatrix m_sublayerTransform; + + // Replica layer used for reflections. + RefPtr<LayerChromium> m_replicaLayer; + + // Transient properties. + OwnPtr<RenderSurfaceChromium> m_renderSurface; + float m_drawOpacity; + bool m_drawOpacityIsAnimating; + + LayerChromium* m_renderTarget; + + WebKit::WebTransformationMatrix m_drawTransform; + WebKit::WebTransformationMatrix m_screenSpaceTransform; + bool m_drawTransformIsAnimating; + bool m_screenSpaceTransformIsAnimating; + + // Uses target surface space. + IntRect m_drawableContentRect; + float m_contentsScale; + + WebKit::WebAnimationDelegate* m_layerAnimationDelegate; + LayerChromiumScrollDelegate* m_layerScrollDelegate; +}; + +void sortLayers(Vector<RefPtr<LayerChromium> >::iterator, Vector<RefPtr<LayerChromium> >::iterator, void*); + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/LayerPainterChromium.h b/cc/LayerPainterChromium.h new file mode 100644 index 0000000..98eadf5 --- /dev/null +++ b/cc/LayerPainterChromium.h @@ -0,0 +1,27 @@ +// 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. + + +#ifndef LayerPainterChromium_h +#define LayerPainterChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +class SkCanvas; + +namespace WebCore { + +class FloatRect; +class IntRect; + +class LayerPainterChromium { +public: + virtual ~LayerPainterChromium() { } + virtual void paint(SkCanvas*, const IntRect& contentRect, FloatRect& opaque) = 0; +}; + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) +#endif // LayerPainterChromium_h + diff --git a/cc/LayerTextureSubImage.cpp b/cc/LayerTextureSubImage.cpp new file mode 100644 index 0000000..f555fa1 --- /dev/null +++ b/cc/LayerTextureSubImage.cpp @@ -0,0 +1,109 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "LayerTextureSubImage.h" + +#include "CCRendererGL.h" // For the GLC() macro. +#include "Extensions3DChromium.h" +#include "TraceEvent.h" +#include <public/WebGraphicsContext3D.h> + +using WebKit::WebGraphicsContext3D; + +namespace WebCore { + +LayerTextureSubImage::LayerTextureSubImage(bool useMapTexSubImage) + : m_useMapTexSubImage(useMapTexSubImage) + , m_subImageSize(0) +{ +} + +LayerTextureSubImage::~LayerTextureSubImage() +{ +} + +void LayerTextureSubImage::upload(const uint8_t* image, const IntRect& imageRect, + const IntRect& sourceRect, const IntSize& destOffset, + GC3Denum format, WebGraphicsContext3D* context) +{ + if (m_useMapTexSubImage) + uploadWithMapTexSubImage(image, imageRect, sourceRect, destOffset, format, context); + else + uploadWithTexSubImage(image, imageRect, sourceRect, destOffset, format, context); +} + +void LayerTextureSubImage::uploadWithTexSubImage(const uint8_t* image, const IntRect& imageRect, + const IntRect& sourceRect, const IntSize& destOffset, + GC3Denum format, WebGraphicsContext3D* context) +{ + TRACE_EVENT0("cc", "LayerTextureSubImage::uploadWithTexSubImage"); + + // Offset from image-rect to source-rect. + IntPoint offset(sourceRect.x() - imageRect.x(), sourceRect.y() - imageRect.y()); + + const uint8_t* pixelSource; + if (imageRect.width() == sourceRect.width() && !offset.x()) + pixelSource = &image[4 * offset.y() * imageRect.width()]; + else { + size_t neededSize = 4 * sourceRect.width() * sourceRect.height(); + if (m_subImageSize < neededSize) { + m_subImage = adoptArrayPtr(new uint8_t[neededSize]); + m_subImageSize = neededSize; + } + // Strides not equal, so do a row-by-row memcpy from the + // paint results into a temp buffer for uploading. + for (int row = 0; row < sourceRect.height(); ++row) + memcpy(&m_subImage[sourceRect.width() * 4 * row], + &image[4 * (offset.x() + (offset.y() + row) * imageRect.width())], + sourceRect.width() * 4); + + pixelSource = &m_subImage[0]; + } + + GLC(context, context->texSubImage2D(GraphicsContext3D::TEXTURE_2D, 0, destOffset.width(), destOffset.height(), sourceRect.width(), sourceRect.height(), format, GraphicsContext3D::UNSIGNED_BYTE, pixelSource)); +} + +void LayerTextureSubImage::uploadWithMapTexSubImage(const uint8_t* image, const IntRect& imageRect, + const IntRect& sourceRect, const IntSize& destOffset, + GC3Denum format, WebGraphicsContext3D* context) +{ + TRACE_EVENT0("cc", "LayerTextureSubImage::uploadWithMapTexSubImage"); + // Offset from image-rect to source-rect. + IntPoint offset(sourceRect.x() - imageRect.x(), sourceRect.y() - imageRect.y()); + + // Upload tile data via a mapped transfer buffer + uint8_t* pixelDest = static_cast<uint8_t*>(context->mapTexSubImage2DCHROMIUM(GraphicsContext3D::TEXTURE_2D, 0, destOffset.width(), destOffset.height(), sourceRect.width(), sourceRect.height(), format, GraphicsContext3D::UNSIGNED_BYTE, Extensions3DChromium::WRITE_ONLY)); + + if (!pixelDest) { + uploadWithTexSubImage(image, imageRect, sourceRect, destOffset, format, context); + return; + } + + unsigned int componentsPerPixel; + unsigned int bytesPerComponent; + if (!GraphicsContext3D::computeFormatAndTypeParameters(format, GraphicsContext3D::UNSIGNED_BYTE, &componentsPerPixel, &bytesPerComponent)) { + ASSERT_NOT_REACHED(); + return; + } + + if (imageRect.width() == sourceRect.width() && !offset.x()) + memcpy(pixelDest, &image[offset.y() * imageRect.width() * componentsPerPixel * bytesPerComponent], imageRect.width() * sourceRect.height() * componentsPerPixel * bytesPerComponent); + else { + // Strides not equal, so do a row-by-row memcpy from the + // paint results into the pixelDest + for (int row = 0; row < sourceRect.height(); ++row) + memcpy(&pixelDest[sourceRect.width() * row * componentsPerPixel * bytesPerComponent], + &image[4 * (offset.x() + (offset.y() + row) * imageRect.width())], + sourceRect.width() * componentsPerPixel * bytesPerComponent); + } + GLC(context, context->unmapTexSubImage2DCHROMIUM(pixelDest)); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/LayerTextureSubImage.h b/cc/LayerTextureSubImage.h new file mode 100644 index 0000000..bb63bf1 --- /dev/null +++ b/cc/LayerTextureSubImage.h @@ -0,0 +1,46 @@ +// 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. + + +#ifndef LayerTextureSubImage_h +#define LayerTextureSubImage_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "GraphicsTypes3D.h" +#include "IntRect.h" +#include "IntSize.h" +#include <wtf/OwnArrayPtr.h> + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace WebCore { + +class LayerTextureSubImage { +public: + explicit LayerTextureSubImage(bool useMapSubForUpload); + ~LayerTextureSubImage(); + + void upload(const uint8_t* image, const IntRect& imageRect, + const IntRect& sourceRect, const IntSize& destOffset, + GC3Denum format, WebKit::WebGraphicsContext3D*); + +private: + void uploadWithTexSubImage(const uint8_t* image, const IntRect& imageRect, + const IntRect& sourceRect, const IntSize& destOffset, + GC3Denum format, WebKit::WebGraphicsContext3D*); + void uploadWithMapTexSubImage(const uint8_t* image, const IntRect& imageRect, + const IntRect& sourceRect, const IntSize& destOffset, + GC3Denum format, WebKit::WebGraphicsContext3D*); + + bool m_useMapTexSubImage; + size_t m_subImageSize; + OwnArrayPtr<uint8_t> m_subImage; +}; + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) +#endif // LayerTextureSubImage_h diff --git a/cc/LayerTextureUpdater.h b/cc/LayerTextureUpdater.h new file mode 100644 index 0000000..9750498 --- /dev/null +++ b/cc/LayerTextureUpdater.h @@ -0,0 +1,63 @@ +// 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. + + +#ifndef LayerTextureUpdater_h +#define LayerTextureUpdater_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCPrioritizedTexture.h" +#include "GraphicsTypes3D.h" +#include <wtf/RefCounted.h> + +namespace WebCore { + +class IntRect; +class IntSize; +class TextureManager; +struct CCRenderingStats; + +class LayerTextureUpdater : public RefCounted<LayerTextureUpdater> { +public: + // Allows texture uploaders to store per-tile resources. + class Texture { + public: + virtual ~Texture() { } + + CCPrioritizedTexture* texture() { return m_texture.get(); } + void swapTextureWith(OwnPtr<CCPrioritizedTexture>& texture) { m_texture.swap(texture); } + virtual void prepareRect(const IntRect& /* sourceRect */, CCRenderingStats&) { } + virtual void updateRect(CCResourceProvider*, const IntRect& sourceRect, const IntSize& destOffset) = 0; + protected: + explicit Texture(PassOwnPtr<CCPrioritizedTexture> texture) : m_texture(texture) { } + + private: + OwnPtr<CCPrioritizedTexture> m_texture; + }; + + virtual ~LayerTextureUpdater() { } + + enum SampledTexelFormat { + SampledTexelFormatRGBA, + SampledTexelFormatBGRA, + SampledTexelFormatInvalid, + }; + virtual PassOwnPtr<Texture> createTexture(CCPrioritizedTextureManager*) = 0; + // Returns the format of the texel uploaded by this interface. + // This format should not be confused by texture internal format. + // This format specifies the component order in the sampled texel. + // If the format is TexelFormatBGRA, vec4.x is blue and vec4.z is red. + virtual SampledTexelFormat sampledTexelFormat(GC3Denum textureFormat) = 0; + // The |resultingOpaqueRect| gives back a region of the layer that was painted opaque. If the layer is marked opaque in the updater, + // then this region should be ignored in preference for the entire layer's area. + virtual void prepareToUpdate(const IntRect& contentRect, const IntSize& tileSize, float contentsWidthScale, float contentsHeightScale, IntRect& resultingOpaqueRect, CCRenderingStats&) { } + + // Set true by the layer when it is known that the entire output is going to be opaque. + virtual void setOpaque(bool) { } +}; + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) +#endif // LayerTextureUpdater_h diff --git a/cc/PlatformColor.h b/cc/PlatformColor.h new file mode 100644 index 0000000..2898047 --- /dev/null +++ b/cc/PlatformColor.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef PlatformColor_h +#define PlatformColor_h + +#include "Extensions3D.h" +#include "GraphicsContext3D.h" +#include "SkTypes.h" +#include <public/WebGraphicsContext3D.h> + +namespace WebCore { + +class PlatformColor { +public: + static GraphicsContext3D::SourceDataFormat format() + { + return SK_B32_SHIFT ? GraphicsContext3D::SourceFormatRGBA8 : GraphicsContext3D::SourceFormatBGRA8; + } + + // Returns the most efficient texture format for this platform. + static GC3Denum bestTextureFormat(WebKit::WebGraphicsContext3D* context, bool supportsBGRA8888) + { + GC3Denum textureFormat = GraphicsContext3D::RGBA; + switch (format()) { + case GraphicsContext3D::SourceFormatRGBA8: + break; + case GraphicsContext3D::SourceFormatBGRA8: + if (supportsBGRA8888) + textureFormat = Extensions3D::BGRA_EXT; + break; + default: + ASSERT_NOT_REACHED(); + break; + } + return textureFormat; + } + + // Return true if the given texture format has the same component order + // as the color on this platform. + static bool sameComponentOrder(GC3Denum textureFormat) + { + switch (format()) { + case GraphicsContext3D::SourceFormatRGBA8: + return textureFormat == GraphicsContext3D::RGBA; + case GraphicsContext3D::SourceFormatBGRA8: + return textureFormat == Extensions3D::BGRA_EXT; + default: + ASSERT_NOT_REACHED(); + return false; + } + } +}; + +} // namespace WebCore + +#endif diff --git a/cc/ProgramBinding.cpp b/cc/ProgramBinding.cpp new file mode 100644 index 0000000..e61bf78 --- /dev/null +++ b/cc/ProgramBinding.cpp @@ -0,0 +1,149 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "ProgramBinding.h" + +#include "CCRendererGL.h" // For the GLC() macro. +#include "GeometryBinding.h" +#include "GraphicsContext3D.h" +#include "TraceEvent.h" +#include <public/WebGraphicsContext3D.h> +#include <wtf/text/CString.h> + +using WebKit::WebGraphicsContext3D; + +namespace WebCore { + +ProgramBindingBase::ProgramBindingBase() + : m_program(0) + , m_vertexShaderId(0) + , m_fragmentShaderId(0) + , m_initialized(false) +{ +} + +ProgramBindingBase::~ProgramBindingBase() +{ + // If you hit these asserts, you initialized but forgot to call cleanup(). + ASSERT(!m_program); + ASSERT(!m_vertexShaderId); + ASSERT(!m_fragmentShaderId); + ASSERT(!m_initialized); +} + +static bool contextLost(WebGraphicsContext3D* context) +{ + return (context->getGraphicsResetStatusARB() != GraphicsContext3D::NO_ERROR); +} + + +void ProgramBindingBase::init(WebGraphicsContext3D* context, const String& vertexShader, const String& fragmentShader) +{ + TRACE_EVENT0("cc", "ProgramBindingBase::init"); + m_vertexShaderId = loadShader(context, GraphicsContext3D::VERTEX_SHADER, vertexShader); + if (!m_vertexShaderId) { + if (!contextLost(context)) + LOG_ERROR("Failed to create vertex shader"); + return; + } + + m_fragmentShaderId = loadShader(context, GraphicsContext3D::FRAGMENT_SHADER, fragmentShader); + if (!m_fragmentShaderId) { + GLC(context, context->deleteShader(m_vertexShaderId)); + m_vertexShaderId = 0; + if (!contextLost(context)) + LOG_ERROR("Failed to create fragment shader"); + return; + } + + m_program = createShaderProgram(context, m_vertexShaderId, m_fragmentShaderId); + ASSERT(m_program || contextLost(context)); +} + +void ProgramBindingBase::link(WebGraphicsContext3D* context) +{ + GLC(context, context->linkProgram(m_program)); + cleanupShaders(context); +#ifndef NDEBUG + int linked = 0; + GLC(context, context->getProgramiv(m_program, GraphicsContext3D::LINK_STATUS, &linked)); + if (!linked) { + if (!contextLost(context)) + LOG_ERROR("Failed to link shader program"); + GLC(context, context->deleteProgram(m_program)); + return; + } +#endif +} + +void ProgramBindingBase::cleanup(WebGraphicsContext3D* context) +{ + m_initialized = false; + if (!m_program) + return; + + ASSERT(context); + GLC(context, context->deleteProgram(m_program)); + m_program = 0; + + cleanupShaders(context); +} + +unsigned ProgramBindingBase::loadShader(WebGraphicsContext3D* context, unsigned type, const String& shaderSource) +{ + unsigned shader = context->createShader(type); + if (!shader) + return 0; + String sourceString(shaderSource); + GLC(context, context->shaderSource(shader, sourceString.utf8().data())); + GLC(context, context->compileShader(shader)); +#ifndef NDEBUG + int compiled = 0; + GLC(context, context->getShaderiv(shader, GraphicsContext3D::COMPILE_STATUS, &compiled)); + if (!compiled) { + GLC(context, context->deleteShader(shader)); + return 0; + } +#endif + return shader; +} + +unsigned ProgramBindingBase::createShaderProgram(WebGraphicsContext3D* context, unsigned vertexShader, unsigned fragmentShader) +{ + unsigned programObject = context->createProgram(); + if (!programObject) { + if (!contextLost(context)) + LOG_ERROR("Failed to create shader program"); + return 0; + } + + GLC(context, context->attachShader(programObject, vertexShader)); + GLC(context, context->attachShader(programObject, fragmentShader)); + + // Bind the common attrib locations. + GLC(context, context->bindAttribLocation(programObject, GeometryBinding::positionAttribLocation(), "a_position")); + GLC(context, context->bindAttribLocation(programObject, GeometryBinding::texCoordAttribLocation(), "a_texCoord")); + + return programObject; +} + +void ProgramBindingBase::cleanupShaders(WebGraphicsContext3D* context) +{ + if (m_vertexShaderId) { + GLC(context, context->deleteShader(m_vertexShaderId)); + m_vertexShaderId = 0; + } + if (m_fragmentShaderId) { + GLC(context, context->deleteShader(m_fragmentShaderId)); + m_fragmentShaderId = 0; + } +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/ProgramBinding.h b/cc/ProgramBinding.h new file mode 100644 index 0000000..fdf0063 --- /dev/null +++ b/cc/ProgramBinding.h @@ -0,0 +1,84 @@ +// 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. + +#ifndef ProgramBinding_h +#define ProgramBinding_h + +#if USE(ACCELERATED_COMPOSITING) + +#include <wtf/text/WTFString.h> + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace WebCore { + +class ProgramBindingBase { +public: + ProgramBindingBase(); + ~ProgramBindingBase(); + + void init(WebKit::WebGraphicsContext3D*, const String& vertexShader, const String& fragmentShader); + void link(WebKit::WebGraphicsContext3D*); + void cleanup(WebKit::WebGraphicsContext3D*); + + unsigned program() const { ASSERT(m_initialized); return m_program; } + bool initialized() const { return m_initialized; } + +protected: + + unsigned loadShader(WebKit::WebGraphicsContext3D*, unsigned type, const String& shaderSource); + unsigned createShaderProgram(WebKit::WebGraphicsContext3D*, unsigned vertexShader, unsigned fragmentShader); + void cleanupShaders(WebKit::WebGraphicsContext3D*); + + unsigned m_program; + unsigned m_vertexShaderId; + unsigned m_fragmentShaderId; + bool m_initialized; +}; + +template<class VertexShader, class FragmentShader> +class ProgramBinding : public ProgramBindingBase { +public: + explicit ProgramBinding(WebKit::WebGraphicsContext3D* context) + { + ProgramBindingBase::init(context, m_vertexShader.getShaderString(), m_fragmentShader.getShaderString()); + } + + void initialize(WebKit::WebGraphicsContext3D* context, bool usingBindUniform) + { + ASSERT(context); + ASSERT(m_program); + ASSERT(!m_initialized); + + // Need to bind uniforms before linking + if (!usingBindUniform) + link(context); + + int baseUniformIndex = 0; + m_vertexShader.init(context, m_program, usingBindUniform, &baseUniformIndex); + m_fragmentShader.init(context, m_program, usingBindUniform, &baseUniformIndex); + + // Link after binding uniforms + if (usingBindUniform) + link(context); + + m_initialized = true; + } + + const VertexShader& vertexShader() const { return m_vertexShader; } + const FragmentShader& fragmentShader() const { return m_fragmentShader; } + +private: + + VertexShader m_vertexShader; + FragmentShader m_fragmentShader; +}; + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) + +#endif @@ -1,2 +1,17 @@ -The chromium compositor code will go here. For now, this directory is just a -placeholder to make gclient happy. +This is the chromium compositor implementation. Currently it's in state of +transition from the WebKit repository to chromium, so there are only stubs +here that are not compiled in by default. + +To try this out, do the following: + +0.) Run the 'copyfiles.py' script in this directory to sync the cc files from + their current location in WebKit into this directory. +1.) Set the gyp variable 'use_libcc_for_compositor=1' and run gyp: + ./build/gyp_chromium -Duse_libcc_for_compositor=1 +2.) Build the 'cc' target to build just the compositor library, or build + 'cc_unittests' for the unit tests. + +Notes about the component=shared_library build: +Because the compositor currently depends on non-exported symbols from inside +WebKit, in the shared library build the cc library links into WebKit.dll. +The unit tests don't currently work at all in the component build. diff --git a/cc/RateLimiter.cpp b/cc/RateLimiter.cpp new file mode 100644 index 0000000..910a33c --- /dev/null +++ b/cc/RateLimiter.cpp @@ -0,0 +1,86 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) +#include "RateLimiter.h" + +#include "CCProxy.h" +#include "CCThread.h" +#include "TraceEvent.h" +#include <public/WebGraphicsContext3D.h> + +namespace WebCore { + +class RateLimiter::Task : public CCThread::Task { +public: + static PassOwnPtr<Task> create(RateLimiter* rateLimiter) + { + return adoptPtr(new Task(rateLimiter)); + } + virtual ~Task() { } + +private: + explicit Task(RateLimiter* rateLimiter) + : CCThread::Task(this) + , m_rateLimiter(rateLimiter) + { + } + + virtual void performTask() OVERRIDE + { + m_rateLimiter->rateLimitContext(); + } + + RefPtr<RateLimiter> m_rateLimiter; +}; + +PassRefPtr<RateLimiter> RateLimiter::create(WebKit::WebGraphicsContext3D* context, RateLimiterClient *client) +{ + return adoptRef(new RateLimiter(context, client)); +} + +RateLimiter::RateLimiter(WebKit::WebGraphicsContext3D* context, RateLimiterClient *client) + : m_context(context) + , m_active(false) + , m_client(client) +{ + ASSERT(context); +} + +RateLimiter::~RateLimiter() +{ +} + +void RateLimiter::start() +{ + if (m_active) + return; + + TRACE_EVENT0("cc", "RateLimiter::start"); + m_active = true; + CCProxy::mainThread()->postTask(RateLimiter::Task::create(this)); +} + +void RateLimiter::stop() +{ + TRACE_EVENT0("cc", "RateLimiter::stop"); + m_client = 0; +} + +void RateLimiter::rateLimitContext() +{ + if (!m_client) + return; + + TRACE_EVENT0("cc", "RateLimiter::rateLimitContext"); + + m_active = false; + m_client->rateLimit(); + m_context->rateLimitOffscreenContextCHROMIUM(); +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/RateLimiter.h b/cc/RateLimiter.h new file mode 100644 index 0000000..8cb8a1d --- /dev/null +++ b/cc/RateLimiter.h @@ -0,0 +1,53 @@ +// 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. + +#ifndef RateLimiter_h +#define RateLimiter_h + +#if USE(ACCELERATED_COMPOSITING) + +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace WebCore { + +class RateLimiterClient { +public: + virtual void rateLimit() = 0; +}; + +// A RateLimiter can be used to make sure that a single context does not dominate all execution time. +// To use, construct a RateLimiter class around the context and call start() whenever calls are made on the +// context outside of normal flow control. RateLimiter will block if the context is too far ahead of the +// compositor. +class RateLimiter : public RefCounted<RateLimiter> { +public: + static PassRefPtr<RateLimiter> create(WebKit::WebGraphicsContext3D*, RateLimiterClient*); + ~RateLimiter(); + + void start(); + + // Context and client will not be accessed after stop(). + void stop(); + +private: + RateLimiter(WebKit::WebGraphicsContext3D*, RateLimiterClient*); + + class Task; + friend class Task; + void rateLimitContext(); + + WebKit::WebGraphicsContext3D* m_context; + bool m_active; + RateLimiterClient *m_client; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/RenderSurfaceChromium.cpp b/cc/RenderSurfaceChromium.cpp new file mode 100644 index 0000000..7f207c4 --- /dev/null +++ b/cc/RenderSurfaceChromium.cpp @@ -0,0 +1,43 @@ +// 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "RenderSurfaceChromium.h" + +#include "CCMathUtil.h" +#include "LayerChromium.h" +#include <public/WebTransformationMatrix.h> +#include <wtf/text/CString.h> + +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +RenderSurfaceChromium::RenderSurfaceChromium(LayerChromium* owningLayer) + : m_owningLayer(owningLayer) + , m_drawOpacity(1) + , m_drawOpacityIsAnimating(false) + , m_targetSurfaceTransformsAreAnimating(false) + , m_screenSpaceTransformsAreAnimating(false) + , m_nearestAncestorThatMovesPixels(0) +{ +} + +RenderSurfaceChromium::~RenderSurfaceChromium() +{ +} + +FloatRect RenderSurfaceChromium::drawableContentRect() const +{ + FloatRect drawableContentRect = CCMathUtil::mapClippedRect(m_drawTransform, m_contentRect); + if (m_owningLayer->hasReplica()) + drawableContentRect.unite(CCMathUtil::mapClippedRect(m_replicaDrawTransform, m_contentRect)); + return drawableContentRect; +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/RenderSurfaceChromium.h b/cc/RenderSurfaceChromium.h new file mode 100644 index 0000000..bd27b1c --- /dev/null +++ b/cc/RenderSurfaceChromium.h @@ -0,0 +1,99 @@ +// 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. + + +#ifndef RenderSurfaceChromium_h +#define RenderSurfaceChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "FloatRect.h" +#include "IntRect.h" +#include <public/WebTransformationMatrix.h> +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class LayerChromium; + +class RenderSurfaceChromium { + WTF_MAKE_NONCOPYABLE(RenderSurfaceChromium); +public: + explicit RenderSurfaceChromium(LayerChromium*); + ~RenderSurfaceChromium(); + + // Returns the rect that encloses the RenderSurface including any reflection. + FloatRect drawableContentRect() const; + + const IntRect& contentRect() const { return m_contentRect; } + void setContentRect(const IntRect& contentRect) { m_contentRect = contentRect; } + + float drawOpacity() const { return m_drawOpacity; } + void setDrawOpacity(float drawOpacity) { m_drawOpacity = drawOpacity; } + + bool drawOpacityIsAnimating() const { return m_drawOpacityIsAnimating; } + void setDrawOpacityIsAnimating(bool drawOpacityIsAnimating) { m_drawOpacityIsAnimating = drawOpacityIsAnimating; } + + // This goes from content space with the origin in the center of the rect being transformed to the target space with the origin in the top left of the + // rect being transformed. Position the rect so that the origin is in the center of it before applying this transform. + const WebKit::WebTransformationMatrix& drawTransform() const { return m_drawTransform; } + void setDrawTransform(const WebKit::WebTransformationMatrix& drawTransform) { m_drawTransform = drawTransform; } + + const WebKit::WebTransformationMatrix& screenSpaceTransform() const { return m_screenSpaceTransform; } + void setScreenSpaceTransform(const WebKit::WebTransformationMatrix& screenSpaceTransform) { m_screenSpaceTransform = screenSpaceTransform; } + + const WebKit::WebTransformationMatrix& replicaDrawTransform() const { return m_replicaDrawTransform; } + void setReplicaDrawTransform(const WebKit::WebTransformationMatrix& replicaDrawTransform) { m_replicaDrawTransform = replicaDrawTransform; } + + const WebKit::WebTransformationMatrix& replicaScreenSpaceTransform() const { return m_replicaScreenSpaceTransform; } + void setReplicaScreenSpaceTransform(const WebKit::WebTransformationMatrix& replicaScreenSpaceTransform) { m_replicaScreenSpaceTransform = replicaScreenSpaceTransform; } + + bool targetSurfaceTransformsAreAnimating() const { return m_targetSurfaceTransformsAreAnimating; } + void setTargetSurfaceTransformsAreAnimating(bool animating) { m_targetSurfaceTransformsAreAnimating = animating; } + bool screenSpaceTransformsAreAnimating() const { return m_screenSpaceTransformsAreAnimating; } + void setScreenSpaceTransformsAreAnimating(bool animating) { m_screenSpaceTransformsAreAnimating = animating; } + + const IntRect& clipRect() const { return m_clipRect; } + void setClipRect(const IntRect& clipRect) { m_clipRect = clipRect; } + + void clearLayerList() { m_layerList.clear(); } + Vector<RefPtr<LayerChromium> >& layerList() { return m_layerList; } + + void setNearestAncestorThatMovesPixels(RenderSurfaceChromium* surface) { m_nearestAncestorThatMovesPixels = surface; } + const RenderSurfaceChromium* nearestAncestorThatMovesPixels() const { return m_nearestAncestorThatMovesPixels; } + +private: + LayerChromium* m_owningLayer; + + // Uses this surface's space. + IntRect m_contentRect; + + float m_drawOpacity; + bool m_drawOpacityIsAnimating; + WebKit::WebTransformationMatrix m_drawTransform; + WebKit::WebTransformationMatrix m_screenSpaceTransform; + WebKit::WebTransformationMatrix m_replicaDrawTransform; + WebKit::WebTransformationMatrix m_replicaScreenSpaceTransform; + bool m_targetSurfaceTransformsAreAnimating; + bool m_screenSpaceTransformsAreAnimating; + + // Uses the space of the surface's target surface. + IntRect m_clipRect; + + Vector<RefPtr<LayerChromium> > m_layerList; + + // The nearest ancestor target surface that will contain the contents of this surface, and that is going + // to move pixels within the surface (such as with a blur). This can point to itself. + RenderSurfaceChromium* m_nearestAncestorThatMovesPixels; + + // For CCLayerIteratorActions + int m_targetRenderSurfaceLayerIndexHistory; + int m_currentLayerIndexHistory; + friend struct CCLayerIteratorActions; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/ScrollbarLayerChromium.cpp b/cc/ScrollbarLayerChromium.cpp new file mode 100644 index 0000000..2584a26 --- /dev/null +++ b/cc/ScrollbarLayerChromium.cpp @@ -0,0 +1,261 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "ScrollbarLayerChromium.h" + +#include "BitmapCanvasLayerTextureUpdater.h" +#include "CCLayerTreeHost.h" +#include "CCScrollbarLayerImpl.h" +#include "CCTextureUpdateQueue.h" +#include "LayerPainterChromium.h" +#include <public/WebRect.h> + +using WebKit::WebRect; + +namespace WebCore { + +PassOwnPtr<CCLayerImpl> ScrollbarLayerChromium::createCCLayerImpl() +{ + return CCScrollbarLayerImpl::create(id()); +} + +PassRefPtr<ScrollbarLayerChromium> ScrollbarLayerChromium::create(PassOwnPtr<WebKit::WebScrollbar> scrollbar, WebKit::WebScrollbarThemePainter painter, PassOwnPtr<WebKit::WebScrollbarThemeGeometry> geometry, int scrollLayerId) +{ + return adoptRef(new ScrollbarLayerChromium(scrollbar, painter, geometry, scrollLayerId)); +} + +ScrollbarLayerChromium::ScrollbarLayerChromium(PassOwnPtr<WebKit::WebScrollbar> scrollbar, WebKit::WebScrollbarThemePainter painter, PassOwnPtr<WebKit::WebScrollbarThemeGeometry> geometry, int scrollLayerId) + : m_scrollbar(scrollbar) + , m_painter(painter) + , m_geometry(geometry) + , m_scrollLayerId(scrollLayerId) + , m_textureFormat(GraphicsContext3D::INVALID_ENUM) +{ +} + +void ScrollbarLayerChromium::pushPropertiesTo(CCLayerImpl* layer) +{ + LayerChromium::pushPropertiesTo(layer); + + CCScrollbarLayerImpl* scrollbarLayer = static_cast<CCScrollbarLayerImpl*>(layer); + + if (!scrollbarLayer->scrollbarGeometry()) + scrollbarLayer->setScrollbarGeometry(adoptPtr(m_geometry->clone())); + + scrollbarLayer->setScrollbarData(m_scrollbar.get()); + + if (m_backTrack && m_backTrack->texture()->haveBackingTexture()) + scrollbarLayer->setBackTrackResourceId(m_backTrack->texture()->resourceId()); + else + scrollbarLayer->setBackTrackResourceId(0); + + if (m_foreTrack && m_foreTrack->texture()->haveBackingTexture()) + scrollbarLayer->setForeTrackResourceId(m_foreTrack->texture()->resourceId()); + else + scrollbarLayer->setForeTrackResourceId(0); + + if (m_thumb && m_thumb->texture()->haveBackingTexture()) + scrollbarLayer->setThumbResourceId(m_thumb->texture()->resourceId()); + else + scrollbarLayer->setThumbResourceId(0); +} + +class ScrollbarBackgroundPainter : public LayerPainterChromium { + WTF_MAKE_NONCOPYABLE(ScrollbarBackgroundPainter); +public: + static PassOwnPtr<ScrollbarBackgroundPainter> create(WebKit::WebScrollbar* scrollbar, WebKit::WebScrollbarThemePainter painter, WebKit::WebScrollbarThemeGeometry* geometry, WebKit::WebScrollbar::ScrollbarPart trackPart) + { + return adoptPtr(new ScrollbarBackgroundPainter(scrollbar, painter, geometry, trackPart)); + } + + virtual void paint(SkCanvas* skCanvas, const IntRect& contentRect, FloatRect&) OVERRIDE + { + WebKit::WebCanvas* canvas = skCanvas; + // The following is a simplification of ScrollbarThemeComposite::paint. + WebKit::WebRect contentWebRect(contentRect.x(), contentRect.y(), contentRect.width(), contentRect.height()); + m_painter.paintScrollbarBackground(canvas, contentWebRect); + + if (m_geometry->hasButtons(m_scrollbar)) { + WebRect backButtonStartPaintRect = m_geometry->backButtonStartRect(m_scrollbar); + m_painter.paintBackButtonStart(canvas, backButtonStartPaintRect); + + WebRect backButtonEndPaintRect = m_geometry->backButtonEndRect(m_scrollbar); + m_painter.paintBackButtonEnd(canvas, backButtonEndPaintRect); + + WebRect forwardButtonStartPaintRect = m_geometry->forwardButtonStartRect(m_scrollbar); + m_painter.paintForwardButtonStart(canvas, forwardButtonStartPaintRect); + + WebRect forwardButtonEndPaintRect = m_geometry->forwardButtonEndRect(m_scrollbar); + m_painter.paintForwardButtonEnd(canvas, forwardButtonEndPaintRect); + } + + WebRect trackPaintRect = m_geometry->trackRect(m_scrollbar); + m_painter.paintTrackBackground(canvas, trackPaintRect); + + bool thumbPresent = m_geometry->hasThumb(m_scrollbar); + if (thumbPresent) { + if (m_trackPart == WebKit::WebScrollbar::ForwardTrackPart) + m_painter.paintForwardTrackPart(canvas, trackPaintRect); + else + m_painter.paintBackTrackPart(canvas, trackPaintRect); + } + + m_painter.paintTickmarks(canvas, trackPaintRect); + } +private: + ScrollbarBackgroundPainter(WebKit::WebScrollbar* scrollbar, WebKit::WebScrollbarThemePainter painter, WebKit::WebScrollbarThemeGeometry* geometry, WebKit::WebScrollbar::ScrollbarPart trackPart) + : m_scrollbar(scrollbar) + , m_painter(painter) + , m_geometry(geometry) + , m_trackPart(trackPart) + { + } + + WebKit::WebScrollbar* m_scrollbar; + WebKit::WebScrollbarThemePainter m_painter; + WebKit::WebScrollbarThemeGeometry* m_geometry; + WebKit::WebScrollbar::ScrollbarPart m_trackPart; +}; + +class ScrollbarThumbPainter : public LayerPainterChromium { + WTF_MAKE_NONCOPYABLE(ScrollbarThumbPainter); +public: + static PassOwnPtr<ScrollbarThumbPainter> create(WebKit::WebScrollbar* scrollbar, WebKit::WebScrollbarThemePainter painter, WebKit::WebScrollbarThemeGeometry* geometry) + { + return adoptPtr(new ScrollbarThumbPainter(scrollbar, painter, geometry)); + } + + virtual void paint(SkCanvas* skCanvas, const IntRect& contentRect, FloatRect& opaque) OVERRIDE + { + WebKit::WebCanvas* canvas = skCanvas; + + // Consider the thumb to be at the origin when painting. + WebRect thumbRect = m_geometry->thumbRect(m_scrollbar); + thumbRect.x = 0; + thumbRect.y = 0; + m_painter.paintThumb(canvas, thumbRect); + } + +private: + ScrollbarThumbPainter(WebKit::WebScrollbar* scrollbar, WebKit::WebScrollbarThemePainter painter, WebKit::WebScrollbarThemeGeometry* geometry) + : m_scrollbar(scrollbar) + , m_painter(painter) + , m_geometry(geometry) + { + } + + WebKit::WebScrollbar* m_scrollbar; + WebKit::WebScrollbarThemePainter m_painter; + WebKit::WebScrollbarThemeGeometry* m_geometry; +}; + +void ScrollbarLayerChromium::setLayerTreeHost(CCLayerTreeHost* host) +{ + if (!host || host != layerTreeHost()) { + m_backTrackUpdater.clear(); + m_backTrack.clear(); + m_thumbUpdater.clear(); + m_thumb.clear(); + } + + LayerChromium::setLayerTreeHost(host); +} + +void ScrollbarLayerChromium::createTextureUpdaterIfNeeded() +{ + m_textureFormat = layerTreeHost()->rendererCapabilities().bestTextureFormat; + + if (!m_backTrackUpdater) + m_backTrackUpdater = BitmapCanvasLayerTextureUpdater::create(ScrollbarBackgroundPainter::create(m_scrollbar.get(), m_painter, m_geometry.get(), WebKit::WebScrollbar::BackTrackPart)); + if (!m_backTrack) + m_backTrack = m_backTrackUpdater->createTexture(layerTreeHost()->contentsTextureManager()); + + // Only create two-part track if we think the two parts could be different in appearance. + if (m_scrollbar->isCustomScrollbar()) { + if (!m_foreTrackUpdater) + m_foreTrackUpdater = BitmapCanvasLayerTextureUpdater::create(ScrollbarBackgroundPainter::create(m_scrollbar.get(), m_painter, m_geometry.get(), WebKit::WebScrollbar::ForwardTrackPart)); + if (!m_foreTrack) + m_foreTrack = m_foreTrackUpdater->createTexture(layerTreeHost()->contentsTextureManager()); + } + + if (!m_thumbUpdater) + m_thumbUpdater = BitmapCanvasLayerTextureUpdater::create(ScrollbarThumbPainter::create(m_scrollbar.get(), m_painter, m_geometry.get())); + if (!m_thumb) + m_thumb = m_thumbUpdater->createTexture(layerTreeHost()->contentsTextureManager()); +} + +void ScrollbarLayerChromium::updatePart(LayerTextureUpdater* painter, LayerTextureUpdater::Texture* texture, const IntRect& rect, CCTextureUpdateQueue& queue, CCRenderingStats& stats) +{ + // Skip painting and uploading if there are no invalidations and + // we already have valid texture data. + if (texture->texture()->haveBackingTexture() + && texture->texture()->size() == rect.size() + && m_updateRect.isEmpty()) + return; + + // We should always have enough memory for UI. + ASSERT(texture->texture()->canAcquireBackingTexture()); + if (!texture->texture()->canAcquireBackingTexture()) + return; + + // Paint and upload the entire part. + IntRect paintedOpaqueRect; + painter->prepareToUpdate(rect, rect.size(), 1, 1, paintedOpaqueRect, stats); + texture->prepareRect(rect, stats); + + IntSize destOffset(0, 0); + TextureUploader::Parameters upload = { texture, rect, destOffset }; + queue.appendFullUpload(upload); +} + + +void ScrollbarLayerChromium::setTexturePriorities(const CCPriorityCalculator&) +{ + if (contentBounds().isEmpty()) + return; + + createTextureUpdaterIfNeeded(); + + bool drawsToRoot = !renderTarget()->parent(); + if (m_backTrack) { + m_backTrack->texture()->setDimensions(contentBounds(), m_textureFormat); + m_backTrack->texture()->setRequestPriority(CCPriorityCalculator::uiPriority(drawsToRoot)); + } + if (m_foreTrack) { + m_foreTrack->texture()->setDimensions(contentBounds(), m_textureFormat); + m_foreTrack->texture()->setRequestPriority(CCPriorityCalculator::uiPriority(drawsToRoot)); + } + if (m_thumb) { + WebKit::WebRect thumbRect = m_geometry->thumbRect(m_scrollbar.get()); + m_thumb->texture()->setDimensions(IntSize(thumbRect.width, thumbRect.height), m_textureFormat); + m_thumb->texture()->setRequestPriority(CCPriorityCalculator::uiPriority(drawsToRoot)); + } +} + +void ScrollbarLayerChromium::update(CCTextureUpdateQueue& queue, const CCOcclusionTracker*, CCRenderingStats& stats) +{ + if (contentBounds().isEmpty()) + return; + + createTextureUpdaterIfNeeded(); + + IntPoint scrollbarOrigin(m_scrollbar->location().x, m_scrollbar->location().y); + IntRect contentRect(scrollbarOrigin, contentBounds()); + updatePart(m_backTrackUpdater.get(), m_backTrack.get(), contentRect, queue, stats); + if (m_foreTrack && m_foreTrackUpdater) + updatePart(m_foreTrackUpdater.get(), m_foreTrack.get(), contentRect, queue, stats); + + // Consider the thumb to be at the origin when painting. + WebKit::WebRect thumbRect = m_geometry->thumbRect(m_scrollbar.get()); + IntRect originThumbRect = IntRect(0, 0, thumbRect.width, thumbRect.height); + if (!originThumbRect.isEmpty()) + updatePart(m_thumbUpdater.get(), m_thumb.get(), originThumbRect, queue, stats); +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/ScrollbarLayerChromium.h b/cc/ScrollbarLayerChromium.h new file mode 100644 index 0000000..f5f7d78 --- /dev/null +++ b/cc/ScrollbarLayerChromium.h @@ -0,0 +1,66 @@ +// Copyright 2012 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. + + +#ifndef ScrollbarLayerChromium_h +#define ScrollbarLayerChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "LayerChromium.h" +#include "LayerTextureUpdater.h" +#include <public/WebScrollbar.h> +#include <public/WebScrollbarThemeGeometry.h> +#include <public/WebScrollbarThemePainter.h> + +namespace WebCore { + +class Scrollbar; +class ScrollbarThemeComposite; +class CCTextureUpdateQueue; + +class ScrollbarLayerChromium : public LayerChromium { +public: + virtual PassOwnPtr<CCLayerImpl> createCCLayerImpl() OVERRIDE; + static PassRefPtr<ScrollbarLayerChromium> create(PassOwnPtr<WebKit::WebScrollbar>, WebKit::WebScrollbarThemePainter, PassOwnPtr<WebKit::WebScrollbarThemeGeometry>, int scrollLayerId); + + // LayerChromium interface + virtual void setTexturePriorities(const CCPriorityCalculator&) OVERRIDE; + virtual void update(CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&) OVERRIDE; + virtual void setLayerTreeHost(CCLayerTreeHost*) OVERRIDE; + virtual void pushPropertiesTo(CCLayerImpl*) OVERRIDE; + + int scrollLayerId() const { return m_scrollLayerId; } + void setScrollLayerId(int id) { m_scrollLayerId = id; } + + virtual ScrollbarLayerChromium* toScrollbarLayerChromium() OVERRIDE { return this; } + +protected: + ScrollbarLayerChromium(PassOwnPtr<WebKit::WebScrollbar>, WebKit::WebScrollbarThemePainter, PassOwnPtr<WebKit::WebScrollbarThemeGeometry>, int scrollLayerId); + +private: + void updatePart(LayerTextureUpdater*, LayerTextureUpdater::Texture*, const IntRect&, CCTextureUpdateQueue&, CCRenderingStats&); + void createTextureUpdaterIfNeeded(); + + OwnPtr<WebKit::WebScrollbar> m_scrollbar; + WebKit::WebScrollbarThemePainter m_painter; + OwnPtr<WebKit::WebScrollbarThemeGeometry> m_geometry; + int m_scrollLayerId; + + GC3Denum m_textureFormat; + + RefPtr<LayerTextureUpdater> m_backTrackUpdater; + RefPtr<LayerTextureUpdater> m_foreTrackUpdater; + RefPtr<LayerTextureUpdater> m_thumbUpdater; + + // All the parts of the scrollbar except the thumb + OwnPtr<LayerTextureUpdater::Texture> m_backTrack; + OwnPtr<LayerTextureUpdater::Texture> m_foreTrack; + OwnPtr<LayerTextureUpdater::Texture> m_thumb; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/ShaderChromium.cpp b/cc/ShaderChromium.cpp new file mode 100644 index 0000000..270a11d --- /dev/null +++ b/cc/ShaderChromium.cpp @@ -0,0 +1,884 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "ShaderChromium.h" + +#include <public/WebGraphicsContext3D.h> + +#define SHADER0(Src) #Src +#define SHADER(Src) SHADER0(Src) + +using WebKit::WebGraphicsContext3D; + +namespace WebCore { + +namespace { + +static void getProgramUniformLocations(WebGraphicsContext3D* context, unsigned program, const char** shaderUniforms, size_t count, size_t maxLocations, int* locations, bool usingBindUniform, int* baseUniformIndex) +{ + for (size_t uniformIndex = 0; uniformIndex < count; uniformIndex ++) { + ASSERT(uniformIndex < maxLocations); + + if (usingBindUniform) { + locations[uniformIndex] = (*baseUniformIndex)++; + context->bindUniformLocationCHROMIUM(program, locations[uniformIndex], shaderUniforms[uniformIndex]); + } else + locations[uniformIndex] = context->getUniformLocation(program, shaderUniforms[uniformIndex]); + } +} + +} + +VertexShaderPosTex::VertexShaderPosTex() + : m_matrixLocation(-1) +{ +} + +void VertexShaderPosTex::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "matrix", + }; + int locations[1]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_matrixLocation = locations[0]; + ASSERT(m_matrixLocation != -1); +} + +String VertexShaderPosTex::getShaderString() const +{ + return SHADER( + attribute vec4 a_position; + attribute vec2 a_texCoord; + uniform mat4 matrix; + varying vec2 v_texCoord; + void main() + { + gl_Position = matrix * a_position; + v_texCoord = a_texCoord; + } + ); +} + +VertexShaderPosTexYUVStretch::VertexShaderPosTexYUVStretch() + : m_matrixLocation(-1) + , m_yWidthScaleFactorLocation(-1) + , m_uvWidthScaleFactorLocation(-1) +{ +} + +void VertexShaderPosTexYUVStretch::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "matrix", + "y_widthScaleFactor", + "uv_widthScaleFactor", + }; + int locations[3]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_matrixLocation = locations[0]; + m_yWidthScaleFactorLocation = locations[1]; + m_uvWidthScaleFactorLocation = locations[2]; + ASSERT(m_matrixLocation != -1 && m_yWidthScaleFactorLocation != -1 && m_uvWidthScaleFactorLocation != -1); +} + +String VertexShaderPosTexYUVStretch::getShaderString() const +{ + return SHADER( + precision mediump float; + attribute vec4 a_position; + attribute vec2 a_texCoord; + uniform mat4 matrix; + varying vec2 y_texCoord; + varying vec2 uv_texCoord; + uniform float y_widthScaleFactor; + uniform float uv_widthScaleFactor; + void main() + { + gl_Position = matrix * a_position; + y_texCoord = vec2(y_widthScaleFactor * a_texCoord.x, a_texCoord.y); + uv_texCoord = vec2(uv_widthScaleFactor * a_texCoord.x, a_texCoord.y); + } + ); +} + +VertexShaderPos::VertexShaderPos() + : m_matrixLocation(-1) +{ +} + +void VertexShaderPos::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "matrix", + }; + int locations[1]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_matrixLocation = locations[0]; + ASSERT(m_matrixLocation != -1); +} + +String VertexShaderPos::getShaderString() const +{ + return SHADER( + attribute vec4 a_position; + uniform mat4 matrix; + void main() + { + gl_Position = matrix * a_position; + } + ); +} + +VertexShaderPosTexTransform::VertexShaderPosTexTransform() + : m_matrixLocation(-1) + , m_texTransformLocation(-1) +{ +} + +void VertexShaderPosTexTransform::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "matrix", + "texTransform", + }; + int locations[2]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_matrixLocation = locations[0]; + m_texTransformLocation = locations[1]; + ASSERT(m_matrixLocation != -1 && m_texTransformLocation != -1); +} + +String VertexShaderPosTexTransform::getShaderString() const +{ + return SHADER( + attribute vec4 a_position; + attribute vec2 a_texCoord; + uniform mat4 matrix; + uniform vec4 texTransform; + varying vec2 v_texCoord; + void main() + { + gl_Position = matrix * a_position; + v_texCoord = a_texCoord * texTransform.zw + texTransform.xy; + } + ); +} + +VertexShaderQuad::VertexShaderQuad() + : m_matrixLocation(-1) + , m_pointLocation(-1) +{ +} + +String VertexShaderPosTexIdentity::getShaderString() const +{ + return SHADER( + attribute vec4 a_position; + varying vec2 v_texCoord; + void main() + { + gl_Position = a_position; + v_texCoord = (a_position.xy + vec2(1.0)) * 0.5; + } + ); +} + +void VertexShaderQuad::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "matrix", + "point", + }; + int locations[2]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_matrixLocation = locations[0]; + m_pointLocation = locations[1]; + ASSERT(m_matrixLocation != -1 && m_pointLocation != -1); +} + +String VertexShaderQuad::getShaderString() const +{ + return SHADER( + attribute vec4 a_position; + attribute vec2 a_texCoord; + uniform mat4 matrix; + uniform vec2 point[4]; + varying vec2 v_texCoord; + void main() + { + vec2 complement = abs(a_texCoord - 1.0); + vec4 pos = vec4(0.0, 0.0, a_position.z, a_position.w); + pos.xy += (complement.x * complement.y) * point[0]; + pos.xy += (a_texCoord.x * complement.y) * point[1]; + pos.xy += (a_texCoord.x * a_texCoord.y) * point[2]; + pos.xy += (complement.x * a_texCoord.y) * point[3]; + gl_Position = matrix * pos; + v_texCoord = pos.xy + vec2(0.5); + } + ); +} + +VertexShaderTile::VertexShaderTile() + : m_matrixLocation(-1) + , m_pointLocation(-1) + , m_vertexTexTransformLocation(-1) +{ +} + +void VertexShaderTile::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "matrix", + "point", + "vertexTexTransform", + }; + int locations[3]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_matrixLocation = locations[0]; + m_pointLocation = locations[1]; + m_vertexTexTransformLocation = locations[2]; + ASSERT(m_matrixLocation != -1 && m_pointLocation != -1 && m_vertexTexTransformLocation != -1); +} + +String VertexShaderTile::getShaderString() const +{ + return SHADER( + attribute vec4 a_position; + attribute vec2 a_texCoord; + uniform mat4 matrix; + uniform vec2 point[4]; + uniform vec4 vertexTexTransform; + varying vec2 v_texCoord; + void main() + { + vec2 complement = abs(a_texCoord - 1.0); + vec4 pos = vec4(0.0, 0.0, a_position.z, a_position.w); + pos.xy += (complement.x * complement.y) * point[0]; + pos.xy += (a_texCoord.x * complement.y) * point[1]; + pos.xy += (a_texCoord.x * a_texCoord.y) * point[2]; + pos.xy += (complement.x * a_texCoord.y) * point[3]; + gl_Position = matrix * pos; + v_texCoord = pos.xy * vertexTexTransform.zw + vertexTexTransform.xy; + } + ); +} + +VertexShaderVideoTransform::VertexShaderVideoTransform() + : m_matrixLocation(-1) + , m_texMatrixLocation(-1) +{ +} + +bool VertexShaderVideoTransform::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "matrix", + "texMatrix", + }; + int locations[2]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_matrixLocation = locations[0]; + m_texMatrixLocation = locations[1]; + return m_matrixLocation != -1 && m_texMatrixLocation != -1; +} + +String VertexShaderVideoTransform::getShaderString() const +{ + return SHADER( + attribute vec4 a_position; + attribute vec2 a_texCoord; + uniform mat4 matrix; + uniform mat4 texMatrix; + varying vec2 v_texCoord; + void main() + { + gl_Position = matrix * a_position; + v_texCoord = vec2(texMatrix * vec4(a_texCoord.x, 1.0 - a_texCoord.y, 0.0, 1.0)); + } + ); +} + +FragmentTexAlphaBinding::FragmentTexAlphaBinding() + : m_samplerLocation(-1) + , m_alphaLocation(-1) +{ +} + +void FragmentTexAlphaBinding::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "s_texture", + "alpha", + }; + int locations[2]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_samplerLocation = locations[0]; + m_alphaLocation = locations[1]; + ASSERT(m_samplerLocation != -1 && m_alphaLocation != -1); +} + +FragmentTexOpaqueBinding::FragmentTexOpaqueBinding() + : m_samplerLocation(-1) +{ +} + +void FragmentTexOpaqueBinding::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "s_texture", + }; + int locations[1]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_samplerLocation = locations[0]; + ASSERT(m_samplerLocation != -1); +} + +String FragmentShaderRGBATexFlipAlpha::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + uniform float alpha; + void main() + { + vec4 texColor = texture2D(s_texture, vec2(v_texCoord.x, 1.0 - v_texCoord.y)); + gl_FragColor = vec4(texColor.x, texColor.y, texColor.z, texColor.w) * alpha; + } + ); +} + +bool FragmentShaderOESImageExternal::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "s_texture", + }; + int locations[1]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_samplerLocation = locations[0]; + return m_samplerLocation != -1; +} + +String FragmentShaderOESImageExternal::getShaderString() const +{ + // Cannot use the SHADER() macro because of the '#' char + return "#extension GL_OES_EGL_image_external : require \n" + "precision mediump float;\n" + "varying vec2 v_texCoord;\n" + "uniform samplerExternalOES s_texture;\n" + "void main()\n" + "{\n" + " vec4 texColor = texture2D(s_texture, v_texCoord);\n" + " gl_FragColor = vec4(texColor.x, texColor.y, texColor.z, texColor.w);\n" + "}\n"; +} + +String FragmentShaderRGBATexAlpha::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + uniform float alpha; + void main() + { + vec4 texColor = texture2D(s_texture, v_texCoord); + gl_FragColor = texColor * alpha; + } + ); +} + +String FragmentShaderRGBATexRectFlipAlpha::getShaderString() const +{ + // This must be paired with VertexShaderPosTexTransform to pick up the texTransform uniform. + // The necessary #extension preprocessing directive breaks the SHADER and SHADER0 macros. + return "#extension GL_ARB_texture_rectangle : require\n" + "precision mediump float;\n" + "varying vec2 v_texCoord;\n" + "uniform vec4 texTransform;\n" + "uniform sampler2DRect s_texture;\n" + "uniform float alpha;\n" + "void main()\n" + "{\n" + " vec4 texColor = texture2DRect(s_texture, vec2(v_texCoord.x, texTransform.w - v_texCoord.y));\n" + " gl_FragColor = vec4(texColor.x, texColor.y, texColor.z, texColor.w) * alpha;\n" + "}\n"; +} + +String FragmentShaderRGBATexRectAlpha::getShaderString() const +{ + return "#extension GL_ARB_texture_rectangle : require\n" + "precision mediump float;\n" + "varying vec2 v_texCoord;\n" + "uniform sampler2DRect s_texture;\n" + "uniform float alpha;\n" + "void main()\n" + "{\n" + " vec4 texColor = texture2DRect(s_texture, v_texCoord);\n" + " gl_FragColor = texColor * alpha;\n" + "}\n"; +} + +String FragmentShaderRGBATexOpaque::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + void main() + { + vec4 texColor = texture2D(s_texture, v_texCoord); + gl_FragColor = vec4(texColor.rgb, 1.0); + } + ); +} + +String FragmentShaderRGBATex::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + void main() + { + gl_FragColor = texture2D(s_texture, v_texCoord); + } + ); +} + +String FragmentShaderRGBATexSwizzleAlpha::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + uniform float alpha; + void main() + { + vec4 texColor = texture2D(s_texture, v_texCoord); + gl_FragColor = vec4(texColor.z, texColor.y, texColor.x, texColor.w) * alpha; + } + ); +} + +String FragmentShaderRGBATexSwizzleOpaque::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + void main() + { + vec4 texColor = texture2D(s_texture, v_texCoord); + gl_FragColor = vec4(texColor.z, texColor.y, texColor.x, 1.0); + } + ); +} + +FragmentShaderRGBATexAlphaAA::FragmentShaderRGBATexAlphaAA() + : m_samplerLocation(-1) + , m_alphaLocation(-1) + , m_edgeLocation(-1) +{ +} + +void FragmentShaderRGBATexAlphaAA::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "s_texture", + "alpha", + "edge", + }; + int locations[3]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_samplerLocation = locations[0]; + m_alphaLocation = locations[1]; + m_edgeLocation = locations[2]; + ASSERT(m_samplerLocation != -1 && m_alphaLocation != -1 && m_edgeLocation != -1); +} + +String FragmentShaderRGBATexAlphaAA::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + uniform float alpha; + uniform vec3 edge[8]; + void main() + { + vec4 texColor = texture2D(s_texture, v_texCoord); + vec3 pos = vec3(gl_FragCoord.xy, 1); + float a0 = clamp(dot(edge[0], pos), 0.0, 1.0); + float a1 = clamp(dot(edge[1], pos), 0.0, 1.0); + float a2 = clamp(dot(edge[2], pos), 0.0, 1.0); + float a3 = clamp(dot(edge[3], pos), 0.0, 1.0); + float a4 = clamp(dot(edge[4], pos), 0.0, 1.0); + float a5 = clamp(dot(edge[5], pos), 0.0, 1.0); + float a6 = clamp(dot(edge[6], pos), 0.0, 1.0); + float a7 = clamp(dot(edge[7], pos), 0.0, 1.0); + gl_FragColor = texColor * alpha * min(min(a0, a2) * min(a1, a3), min(a4, a6) * min(a5, a7)); + } + ); +} + +FragmentTexClampAlphaAABinding::FragmentTexClampAlphaAABinding() + : m_samplerLocation(-1) + , m_alphaLocation(-1) + , m_fragmentTexTransformLocation(-1) + , m_edgeLocation(-1) +{ +} + +void FragmentTexClampAlphaAABinding::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "s_texture", + "alpha", + "fragmentTexTransform", + "edge", + }; + int locations[4]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_samplerLocation = locations[0]; + m_alphaLocation = locations[1]; + m_fragmentTexTransformLocation = locations[2]; + m_edgeLocation = locations[3]; + ASSERT(m_samplerLocation != -1 && m_alphaLocation != -1 && m_fragmentTexTransformLocation != -1 && m_edgeLocation != -1); +} + +String FragmentShaderRGBATexClampAlphaAA::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + uniform float alpha; + uniform vec4 fragmentTexTransform; + uniform vec3 edge[8]; + void main() + { + vec2 texCoord = clamp(v_texCoord, 0.0, 1.0) * fragmentTexTransform.zw + fragmentTexTransform.xy; + vec4 texColor = texture2D(s_texture, texCoord); + vec3 pos = vec3(gl_FragCoord.xy, 1); + float a0 = clamp(dot(edge[0], pos), 0.0, 1.0); + float a1 = clamp(dot(edge[1], pos), 0.0, 1.0); + float a2 = clamp(dot(edge[2], pos), 0.0, 1.0); + float a3 = clamp(dot(edge[3], pos), 0.0, 1.0); + float a4 = clamp(dot(edge[4], pos), 0.0, 1.0); + float a5 = clamp(dot(edge[5], pos), 0.0, 1.0); + float a6 = clamp(dot(edge[6], pos), 0.0, 1.0); + float a7 = clamp(dot(edge[7], pos), 0.0, 1.0); + gl_FragColor = texColor * alpha * min(min(a0, a2) * min(a1, a3), min(a4, a6) * min(a5, a7)); + } + ); +} + +String FragmentShaderRGBATexClampSwizzleAlphaAA::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + uniform float alpha; + uniform vec4 fragmentTexTransform; + uniform vec3 edge[8]; + void main() + { + vec2 texCoord = clamp(v_texCoord, 0.0, 1.0) * fragmentTexTransform.zw + fragmentTexTransform.xy; + vec4 texColor = texture2D(s_texture, texCoord); + vec3 pos = vec3(gl_FragCoord.xy, 1); + float a0 = clamp(dot(edge[0], pos), 0.0, 1.0); + float a1 = clamp(dot(edge[1], pos), 0.0, 1.0); + float a2 = clamp(dot(edge[2], pos), 0.0, 1.0); + float a3 = clamp(dot(edge[3], pos), 0.0, 1.0); + float a4 = clamp(dot(edge[4], pos), 0.0, 1.0); + float a5 = clamp(dot(edge[5], pos), 0.0, 1.0); + float a6 = clamp(dot(edge[6], pos), 0.0, 1.0); + float a7 = clamp(dot(edge[7], pos), 0.0, 1.0); + gl_FragColor = vec4(texColor.z, texColor.y, texColor.x, texColor.w) * alpha * min(min(a0, a2) * min(a1, a3), min(a4, a6) * min(a5, a7)); + } + ); +} + +FragmentShaderRGBATexAlphaMask::FragmentShaderRGBATexAlphaMask() + : m_samplerLocation(-1) + , m_maskSamplerLocation(-1) + , m_alphaLocation(-1) + , m_maskTexCoordScaleLocation(-1) +{ +} + +void FragmentShaderRGBATexAlphaMask::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "s_texture", + "s_mask", + "alpha", + "maskTexCoordScale", + "maskTexCoordOffset", + }; + int locations[5]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_samplerLocation = locations[0]; + m_maskSamplerLocation = locations[1]; + m_alphaLocation = locations[2]; + m_maskTexCoordScaleLocation = locations[3]; + m_maskTexCoordOffsetLocation = locations[4]; + ASSERT(m_samplerLocation != -1 && m_maskSamplerLocation != -1 && m_alphaLocation != -1); +} + +String FragmentShaderRGBATexAlphaMask::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + uniform sampler2D s_mask; + uniform vec2 maskTexCoordScale; + uniform vec2 maskTexCoordOffset; + uniform float alpha; + void main() + { + vec4 texColor = texture2D(s_texture, v_texCoord); + vec2 maskTexCoord = vec2(maskTexCoordOffset.x + v_texCoord.x * maskTexCoordScale.x, maskTexCoordOffset.y + v_texCoord.y * maskTexCoordScale.y); + vec4 maskColor = texture2D(s_mask, maskTexCoord); + gl_FragColor = vec4(texColor.x, texColor.y, texColor.z, texColor.w) * alpha * maskColor.w; + } + ); +} + +FragmentShaderRGBATexAlphaMaskAA::FragmentShaderRGBATexAlphaMaskAA() + : m_samplerLocation(-1) + , m_maskSamplerLocation(-1) + , m_alphaLocation(-1) + , m_edgeLocation(-1) + , m_maskTexCoordScaleLocation(-1) +{ +} + +void FragmentShaderRGBATexAlphaMaskAA::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "s_texture", + "s_mask", + "alpha", + "edge", + "maskTexCoordScale", + "maskTexCoordOffset", + }; + int locations[6]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_samplerLocation = locations[0]; + m_maskSamplerLocation = locations[1]; + m_alphaLocation = locations[2]; + m_edgeLocation = locations[3]; + m_maskTexCoordScaleLocation = locations[4]; + m_maskTexCoordOffsetLocation = locations[5]; + ASSERT(m_samplerLocation != -1 && m_maskSamplerLocation != -1 && m_alphaLocation != -1 && m_edgeLocation != -1); +} + +String FragmentShaderRGBATexAlphaMaskAA::getShaderString() const +{ + return SHADER( + precision mediump float; + varying vec2 v_texCoord; + uniform sampler2D s_texture; + uniform sampler2D s_mask; + uniform vec2 maskTexCoordScale; + uniform vec2 maskTexCoordOffset; + uniform float alpha; + uniform vec3 edge[8]; + void main() + { + vec4 texColor = texture2D(s_texture, v_texCoord); + vec2 maskTexCoord = vec2(maskTexCoordOffset.x + v_texCoord.x * maskTexCoordScale.x, maskTexCoordOffset.y + v_texCoord.y * maskTexCoordScale.y); + vec4 maskColor = texture2D(s_mask, maskTexCoord); + vec3 pos = vec3(gl_FragCoord.xy, 1); + float a0 = clamp(dot(edge[0], pos), 0.0, 1.0); + float a1 = clamp(dot(edge[1], pos), 0.0, 1.0); + float a2 = clamp(dot(edge[2], pos), 0.0, 1.0); + float a3 = clamp(dot(edge[3], pos), 0.0, 1.0); + float a4 = clamp(dot(edge[4], pos), 0.0, 1.0); + float a5 = clamp(dot(edge[5], pos), 0.0, 1.0); + float a6 = clamp(dot(edge[6], pos), 0.0, 1.0); + float a7 = clamp(dot(edge[7], pos), 0.0, 1.0); + gl_FragColor = vec4(texColor.x, texColor.y, texColor.z, texColor.w) * alpha * maskColor.w * min(min(a0, a2) * min(a1, a3), min(a4, a6) * min(a5, a7)); + } + ); +} + +FragmentShaderYUVVideo::FragmentShaderYUVVideo() + : m_yTextureLocation(-1) + , m_uTextureLocation(-1) + , m_vTextureLocation(-1) + , m_alphaLocation(-1) + , m_ccMatrixLocation(-1) + , m_yuvAdjLocation(-1) +{ +} + +void FragmentShaderYUVVideo::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "y_texture", + "u_texture", + "v_texture", + "alpha", + "cc_matrix", + "yuv_adj", + }; + int locations[6]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_yTextureLocation = locations[0]; + m_uTextureLocation = locations[1]; + m_vTextureLocation = locations[2]; + m_alphaLocation = locations[3]; + m_ccMatrixLocation = locations[4]; + m_yuvAdjLocation = locations[5]; + + ASSERT(m_yTextureLocation != -1 && m_uTextureLocation != -1 && m_vTextureLocation != -1 + && m_alphaLocation != -1 && m_ccMatrixLocation != -1 && m_yuvAdjLocation != -1); +} + +String FragmentShaderYUVVideo::getShaderString() const +{ + return SHADER( + precision mediump float; + precision mediump int; + varying vec2 y_texCoord; + varying vec2 uv_texCoord; + uniform sampler2D y_texture; + uniform sampler2D u_texture; + uniform sampler2D v_texture; + uniform float alpha; + uniform vec3 yuv_adj; + uniform mat3 cc_matrix; + void main() + { + float y_raw = texture2D(y_texture, y_texCoord).x; + float u_unsigned = texture2D(u_texture, uv_texCoord).x; + float v_unsigned = texture2D(v_texture, uv_texCoord).x; + vec3 yuv = vec3(y_raw, u_unsigned, v_unsigned) + yuv_adj; + vec3 rgb = cc_matrix * yuv; + gl_FragColor = vec4(rgb, float(1)) * alpha; + } + ); +} + +FragmentShaderColor::FragmentShaderColor() + : m_colorLocation(-1) +{ +} + +void FragmentShaderColor::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "color", + }; + int locations[1]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_colorLocation = locations[0]; + ASSERT(m_colorLocation != -1); +} + +String FragmentShaderColor::getShaderString() const +{ + return SHADER( + precision mediump float; + uniform vec4 color; + void main() + { + gl_FragColor = color; + } + ); +} + +FragmentShaderCheckerboard::FragmentShaderCheckerboard() + : m_alphaLocation(-1) + , m_texTransformLocation(-1) + , m_frequencyLocation(-1) +{ +} + +void FragmentShaderCheckerboard::init(WebGraphicsContext3D* context, unsigned program, bool usingBindUniform, int* baseUniformIndex) +{ + static const char* shaderUniforms[] = { + "alpha", + "texTransform", + "frequency", + }; + int locations[3]; + + getProgramUniformLocations(context, program, shaderUniforms, WTF_ARRAY_LENGTH(shaderUniforms), WTF_ARRAY_LENGTH(locations), locations, usingBindUniform, baseUniformIndex); + + m_alphaLocation = locations[0]; + m_texTransformLocation = locations[1]; + m_frequencyLocation = locations[2]; + ASSERT(m_alphaLocation != -1 && m_texTransformLocation != -1 && m_frequencyLocation != -1); +} + +String FragmentShaderCheckerboard::getShaderString() const +{ + // Shader based on Example 13-17 of "OpenGL ES 2.0 Programming Guide" + // by Munshi, Ginsburg, Shreiner. + return SHADER( + precision mediump float; + precision mediump int; + varying vec2 v_texCoord; + uniform float alpha; + uniform float frequency; + uniform vec4 texTransform; + void main() + { + vec4 color1 = vec4(1.0, 1.0, 1.0, 1.0); + vec4 color2 = vec4(0.945, 0.945, 0.945, 1.0); + vec2 texCoord = clamp(v_texCoord, 0.0, 1.0) * texTransform.zw + texTransform.xy; + vec2 coord = mod(floor(texCoord * frequency * 2.0), 2.0); + float picker = abs(coord.x - coord.y); + gl_FragColor = mix(color1, color2, picker) * alpha; + } + ); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/ShaderChromium.h b/cc/ShaderChromium.h new file mode 100644 index 0000000..ffb6767 --- /dev/null +++ b/cc/ShaderChromium.h @@ -0,0 +1,351 @@ +// 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. + +#ifndef ShaderChromium_h +#define ShaderChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "SkColorPriv.h" +#include <wtf/text/WTFString.h> + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace WebCore { + +class VertexShaderPosTex { +public: + VertexShaderPosTex(); + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + String getShaderString() const; + + int matrixLocation() const { return m_matrixLocation; } + +private: + int m_matrixLocation; +}; + +class VertexShaderPosTexYUVStretch { +public: + VertexShaderPosTexYUVStretch(); + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + String getShaderString() const; + + int matrixLocation() const { return m_matrixLocation; } + int yWidthScaleFactorLocation() const { return m_yWidthScaleFactorLocation; } + int uvWidthScaleFactorLocation() const { return m_uvWidthScaleFactorLocation; } + +private: + int m_matrixLocation; + int m_yWidthScaleFactorLocation; + int m_uvWidthScaleFactorLocation; +}; + +class VertexShaderPos { +public: + VertexShaderPos(); + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + String getShaderString() const; + + int matrixLocation() const { return m_matrixLocation; } + +private: + int m_matrixLocation; +}; + +class VertexShaderPosTexIdentity { +public: + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex) { } + String getShaderString() const; +}; + +class VertexShaderPosTexTransform { +public: + VertexShaderPosTexTransform(); + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + String getShaderString() const; + + int matrixLocation() const { return m_matrixLocation; } + int texTransformLocation() const { return m_texTransformLocation; } + +private: + int m_matrixLocation; + int m_texTransformLocation; +}; + +class VertexShaderQuad { +public: + VertexShaderQuad(); + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + String getShaderString() const; + + int matrixLocation() const { return m_matrixLocation; } + int pointLocation() const { return m_pointLocation; } + +private: + int m_matrixLocation; + int m_pointLocation; +}; + +class VertexShaderTile { +public: + VertexShaderTile(); + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + String getShaderString() const; + + int matrixLocation() const { return m_matrixLocation; } + int pointLocation() const { return m_pointLocation; } + int vertexTexTransformLocation() const { return m_vertexTexTransformLocation; } + +private: + int m_matrixLocation; + int m_pointLocation; + int m_vertexTexTransformLocation; +}; + +class VertexShaderVideoTransform { +public: + VertexShaderVideoTransform(); + + bool init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + String getShaderString() const; + + int matrixLocation() const { return m_matrixLocation; } + int texMatrixLocation() const { return m_texMatrixLocation; } + +private: + int m_matrixLocation; + int m_texMatrixLocation; +}; + +class FragmentTexAlphaBinding { +public: + FragmentTexAlphaBinding(); + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + int alphaLocation() const { return m_alphaLocation; } + int edgeLocation() const { return -1; } + int fragmentTexTransformLocation() const { return -1; } + int samplerLocation() const { return m_samplerLocation; } + +private: + int m_samplerLocation; + int m_alphaLocation; +}; + +class FragmentTexOpaqueBinding { +public: + FragmentTexOpaqueBinding(); + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + int alphaLocation() const { return -1; } + int edgeLocation() const { return -1; } + int fragmentTexTransformLocation() const { return -1; } + int samplerLocation() const { return m_samplerLocation; } + +private: + int m_samplerLocation; +}; + +class FragmentShaderRGBATexFlipAlpha : public FragmentTexAlphaBinding { +public: + String getShaderString() const; +}; + +class FragmentShaderRGBATexAlpha : public FragmentTexAlphaBinding { +public: + String getShaderString() const; +}; + +class FragmentShaderRGBATexRectFlipAlpha : public FragmentTexAlphaBinding { +public: + String getShaderString() const; +}; + +class FragmentShaderRGBATexRectAlpha : public FragmentTexAlphaBinding { +public: + String getShaderString() const; +}; + +class FragmentShaderRGBATexOpaque : public FragmentTexOpaqueBinding { +public: + String getShaderString() const; +}; + +class FragmentShaderRGBATex : public FragmentTexOpaqueBinding { +public: + String getShaderString() const; +}; + +// Swizzles the red and blue component of sampled texel with alpha. +class FragmentShaderRGBATexSwizzleAlpha : public FragmentTexAlphaBinding { +public: + String getShaderString() const; +}; + +// Swizzles the red and blue component of sampled texel without alpha. +class FragmentShaderRGBATexSwizzleOpaque : public FragmentTexOpaqueBinding { +public: + String getShaderString() const; +}; + +// Fragment shader for external textures. +class FragmentShaderOESImageExternal : public FragmentTexAlphaBinding { +public: + String getShaderString() const; + bool init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); +private: + int m_samplerLocation; +}; + +class FragmentShaderRGBATexAlphaAA { +public: + FragmentShaderRGBATexAlphaAA(); + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + String getShaderString() const; + + int alphaLocation() const { return m_alphaLocation; } + int samplerLocation() const { return m_samplerLocation; } + int edgeLocation() const { return m_edgeLocation; } + +private: + int m_samplerLocation; + int m_alphaLocation; + int m_edgeLocation; +}; + +class FragmentTexClampAlphaAABinding { +public: + FragmentTexClampAlphaAABinding(); + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + int alphaLocation() const { return m_alphaLocation; } + int samplerLocation() const { return m_samplerLocation; } + int fragmentTexTransformLocation() const { return m_fragmentTexTransformLocation; } + int edgeLocation() const { return m_edgeLocation; } + +private: + int m_samplerLocation; + int m_alphaLocation; + int m_fragmentTexTransformLocation; + int m_edgeLocation; +}; + +class FragmentShaderRGBATexClampAlphaAA : public FragmentTexClampAlphaAABinding { +public: + String getShaderString() const; +}; + +// Swizzles the red and blue component of sampled texel. +class FragmentShaderRGBATexClampSwizzleAlphaAA : public FragmentTexClampAlphaAABinding { +public: + String getShaderString() const; +}; + +class FragmentShaderRGBATexAlphaMask { +public: + FragmentShaderRGBATexAlphaMask(); + String getShaderString() const; + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + int alphaLocation() const { return m_alphaLocation; } + int samplerLocation() const { return m_samplerLocation; } + int maskSamplerLocation() const { return m_maskSamplerLocation; } + int maskTexCoordScaleLocation() const { return m_maskTexCoordScaleLocation; } + int maskTexCoordOffsetLocation() const { return m_maskTexCoordOffsetLocation; } + +private: + int m_samplerLocation; + int m_maskSamplerLocation; + int m_alphaLocation; + int m_maskTexCoordScaleLocation; + int m_maskTexCoordOffsetLocation; +}; + +class FragmentShaderRGBATexAlphaMaskAA { +public: + FragmentShaderRGBATexAlphaMaskAA(); + String getShaderString() const; + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + int alphaLocation() const { return m_alphaLocation; } + int samplerLocation() const { return m_samplerLocation; } + int maskSamplerLocation() const { return m_maskSamplerLocation; } + int edgeLocation() const { return m_edgeLocation; } + int maskTexCoordScaleLocation() const { return m_maskTexCoordScaleLocation; } + int maskTexCoordOffsetLocation() const { return m_maskTexCoordOffsetLocation; } + +private: + int m_samplerLocation; + int m_maskSamplerLocation; + int m_alphaLocation; + int m_edgeLocation; + int m_maskTexCoordScaleLocation; + int m_maskTexCoordOffsetLocation; +}; + +class FragmentShaderYUVVideo { +public: + FragmentShaderYUVVideo(); + String getShaderString() const; + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + + int yTextureLocation() const { return m_yTextureLocation; } + int uTextureLocation() const { return m_uTextureLocation; } + int vTextureLocation() const { return m_vTextureLocation; } + int alphaLocation() const { return m_alphaLocation; } + int ccMatrixLocation() const { return m_ccMatrixLocation; } + int yuvAdjLocation() const { return m_yuvAdjLocation; } + +private: + int m_yTextureLocation; + int m_uTextureLocation; + int m_vTextureLocation; + int m_alphaLocation; + int m_ccMatrixLocation; + int m_yuvAdjLocation; +}; + +class FragmentShaderColor { +public: + FragmentShaderColor(); + String getShaderString() const; + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + int colorLocation() const { return m_colorLocation; } + +private: + int m_colorLocation; +}; + +class FragmentShaderCheckerboard { +public: + FragmentShaderCheckerboard(); + String getShaderString() const; + + void init(WebKit::WebGraphicsContext3D*, unsigned program, bool usingBindUniform, int* baseUniformIndex); + int alphaLocation() const { return m_alphaLocation; } + int texTransformLocation() const { return m_texTransformLocation; } + int frequencyLocation() const { return m_frequencyLocation; } +private: + int m_alphaLocation; + int m_texTransformLocation; + int m_frequencyLocation; +}; + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/SkPictureCanvasLayerTextureUpdater.cpp b/cc/SkPictureCanvasLayerTextureUpdater.cpp new file mode 100644 index 0000000..3668d34 --- /dev/null +++ b/cc/SkPictureCanvasLayerTextureUpdater.cpp @@ -0,0 +1,47 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "SkPictureCanvasLayerTextureUpdater.h" + +#include "LayerPainterChromium.h" +#include "SkCanvas.h" +#include "TraceEvent.h" + +namespace WebCore { + +SkPictureCanvasLayerTextureUpdater::SkPictureCanvasLayerTextureUpdater(PassOwnPtr<LayerPainterChromium> painter) + : CanvasLayerTextureUpdater(painter) + , m_layerIsOpaque(false) +{ +} + +SkPictureCanvasLayerTextureUpdater::~SkPictureCanvasLayerTextureUpdater() +{ +} + +void SkPictureCanvasLayerTextureUpdater::prepareToUpdate(const IntRect& contentRect, const IntSize&, float contentsWidthScale, float contentsHeightScale, IntRect& resultingOpaqueRect, CCRenderingStats& stats) +{ + SkCanvas* canvas = m_picture.beginRecording(contentRect.width(), contentRect.height()); + paintContents(canvas, contentRect, contentsWidthScale, contentsHeightScale, resultingOpaqueRect, stats); + m_picture.endRecording(); +} + +void SkPictureCanvasLayerTextureUpdater::drawPicture(SkCanvas* canvas) +{ + TRACE_EVENT0("cc", "SkPictureCanvasLayerTextureUpdater::drawPicture"); + canvas->drawPicture(m_picture); +} + +void SkPictureCanvasLayerTextureUpdater::setOpaque(bool opaque) +{ + m_layerIsOpaque = opaque; +} + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/SkPictureCanvasLayerTextureUpdater.h b/cc/SkPictureCanvasLayerTextureUpdater.h new file mode 100644 index 0000000..2635ff7 --- /dev/null +++ b/cc/SkPictureCanvasLayerTextureUpdater.h @@ -0,0 +1,48 @@ +// 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. + + +#ifndef SkPictureCanvasLayerTextureUpdater_h +#define SkPictureCanvasLayerTextureUpdater_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "CanvasLayerTextureUpdater.h" +#include "SkPicture.h" + +class SkCanvas; + +namespace WebCore { + +class LayerPainterChromium; + +// This class records the contentRect into an SkPicture. Subclasses, provide +// different implementations of tile updating based on this recorded picture. +// The BitmapSkPictureCanvasLayerTextureUpdater and +// FrameBufferSkPictureCanvasLayerTextureUpdater are two examples of such +// implementations. +class SkPictureCanvasLayerTextureUpdater : public CanvasLayerTextureUpdater { +public: + virtual ~SkPictureCanvasLayerTextureUpdater(); + + virtual void setOpaque(bool) OVERRIDE; + +protected: + explicit SkPictureCanvasLayerTextureUpdater(PassOwnPtr<LayerPainterChromium>); + + virtual void prepareToUpdate(const IntRect& contentRect, const IntSize& tileSize, float contentsWidthScale, float contentsHeightScale, IntRect& resultingOpaqueRect, CCRenderingStats&) OVERRIDE; + void drawPicture(SkCanvas*); + + bool layerIsOpaque() const { return m_layerIsOpaque; } + +private: + // Recording canvas. + SkPicture m_picture; + // True when it is known that all output pixels will be opaque. + bool m_layerIsOpaque; +}; + +} // namespace WebCore +#endif // USE(ACCELERATED_COMPOSITING) +#endif // SkPictureCanvasLayerTextureUpdater_h diff --git a/cc/SolidColorLayerChromium.cpp b/cc/SolidColorLayerChromium.cpp new file mode 100644 index 0000000..b3652ba --- /dev/null +++ b/cc/SolidColorLayerChromium.cpp @@ -0,0 +1,36 @@ +// Copyright 2012 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "SolidColorLayerChromium.h" + +#include "CCSolidColorLayerImpl.h" + +namespace WebCore { + +PassOwnPtr<CCLayerImpl> SolidColorLayerChromium::createCCLayerImpl() +{ + return CCSolidColorLayerImpl::create(id()); +} + +PassRefPtr<SolidColorLayerChromium> SolidColorLayerChromium::create() +{ + return adoptRef(new SolidColorLayerChromium()); +} + +SolidColorLayerChromium::SolidColorLayerChromium() + : LayerChromium() +{ +} + +SolidColorLayerChromium::~SolidColorLayerChromium() +{ +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/SolidColorLayerChromium.h b/cc/SolidColorLayerChromium.h new file mode 100644 index 0000000..53ac9dc --- /dev/null +++ b/cc/SolidColorLayerChromium.h @@ -0,0 +1,32 @@ +// Copyright 2012 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. + + +#ifndef SolidColorLayerChromium_h +#define SolidColorLayerChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "LayerChromium.h" + +namespace WebCore { + +// A Layer that renders a solid color. The color is specified by using +// setBackgroundColor() on the base class. +class SolidColorLayerChromium : public LayerChromium { +public: + virtual PassOwnPtr<CCLayerImpl> createCCLayerImpl() OVERRIDE; + static PassRefPtr<SolidColorLayerChromium> create(); + + virtual ~SolidColorLayerChromium(); + +protected: + SolidColorLayerChromium(); +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif + diff --git a/cc/TextureCopier.cpp b/cc/TextureCopier.cpp new file mode 100644 index 0000000..e2cdd4b --- /dev/null +++ b/cc/TextureCopier.cpp @@ -0,0 +1,101 @@ +// Copyright 2012 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 "TextureCopier.h" + +#include "CCRendererGL.h" // For the GLC() macro. +#include "GraphicsContext3D.h" +#include "TraceEvent.h" +#include <public/WebGraphicsContext3D.h> + +namespace WebCore { + +#if USE(ACCELERATED_COMPOSITING) +AcceleratedTextureCopier::AcceleratedTextureCopier(WebKit::WebGraphicsContext3D* context, bool usingBindUniforms) + : m_context(context) + , m_usingBindUniforms(usingBindUniforms) +{ + ASSERT(m_context); + GLC(m_context, m_fbo = m_context->createFramebuffer()); + GLC(m_context, m_positionBuffer = m_context->createBuffer()); + + static const float kPositions[4][4] = { + {-1, -1, 0, 1}, + { 1, -1, 0, 1}, + { 1, 1, 0, 1}, + {-1, 1, 0, 1} + }; + + GLC(m_context, m_context->bindBuffer(GraphicsContext3D::ARRAY_BUFFER, m_positionBuffer)); + GLC(m_context, m_context->bufferData(GraphicsContext3D::ARRAY_BUFFER, sizeof(kPositions), kPositions, GraphicsContext3D::STATIC_DRAW)); + GLC(m_context, m_context->bindBuffer(GraphicsContext3D::ARRAY_BUFFER, 0)); + + m_blitProgram = adoptPtr(new BlitProgram(m_context)); +} + +AcceleratedTextureCopier::~AcceleratedTextureCopier() +{ + if (m_blitProgram) + m_blitProgram->cleanup(m_context); + if (m_positionBuffer) + GLC(m_context, m_context->deleteBuffer(m_positionBuffer)); + if (m_fbo) + GLC(m_context, m_context->deleteFramebuffer(m_fbo)); +} + +void AcceleratedTextureCopier::copyTexture(Parameters parameters) +{ + TRACE_EVENT0("cc", "TextureCopier::copyTexture"); + + // Note: this code does not restore the viewport, bound program, 2D texture, framebuffer, buffer or blend enable. + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_fbo)); + GLC(m_context, m_context->framebufferTexture2D(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::TEXTURE_2D, parameters.destTexture, 0)); + +#if OS(ANDROID) + // Clear destination to improve performance on tiling GPUs. + // TODO: Use EXT_discard_framebuffer or skip clearing if it isn't available. + GLC(m_context, m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT)); +#endif + + GLC(m_context, m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, parameters.sourceTexture)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::NEAREST)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::NEAREST)); + + if (!m_blitProgram->initialized()) + m_blitProgram->initialize(m_context, m_usingBindUniforms); + + // TODO: Use EXT_framebuffer_blit if available. + GLC(m_context, m_context->useProgram(m_blitProgram->program())); + + const int kPositionAttribute = 0; + GLC(m_context, m_context->bindBuffer(GraphicsContext3D::ARRAY_BUFFER, m_positionBuffer)); + GLC(m_context, m_context->vertexAttribPointer(kPositionAttribute, 4, GraphicsContext3D::FLOAT, false, 0, 0)); + GLC(m_context, m_context->enableVertexAttribArray(kPositionAttribute)); + GLC(m_context, m_context->bindBuffer(GraphicsContext3D::ARRAY_BUFFER, 0)); + + GLC(m_context, m_context->viewport(0, 0, parameters.size.width(), parameters.size.height())); + GLC(m_context, m_context->disable(GraphicsContext3D::BLEND)); + GLC(m_context, m_context->drawArrays(GraphicsContext3D::TRIANGLE_FAN, 0, 4)); + + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, GraphicsContext3D::LINEAR)); + GLC(m_context, m_context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, GraphicsContext3D::LINEAR)); + GLC(m_context, m_context->disableVertexAttribArray(kPositionAttribute)); + + GLC(m_context, m_context->useProgram(0)); + + GLC(m_context, m_context->framebufferTexture2D(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::TEXTURE_2D, 0, 0)); + GLC(m_context, m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, 0)); + GLC(m_context, m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, 0)); +} + +void AcceleratedTextureCopier::flush() +{ + GLC(m_context, m_context->flush()); +} + +} + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/TextureCopier.h b/cc/TextureCopier.h new file mode 100644 index 0000000..adffddc --- /dev/null +++ b/cc/TextureCopier.h @@ -0,0 +1,68 @@ +// Copyright 2012 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. + +#ifndef TextureCopier_h +#define TextureCopier_h + +#include "GraphicsContext3D.h" +#include "ProgramBinding.h" +#include "ShaderChromium.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace WebCore { +class IntSize; + +class TextureCopier { +public: + struct Parameters { + unsigned sourceTexture; + unsigned destTexture; + IntSize size; + }; + // Copy the base level contents of |sourceTexture| to |destTexture|. Both texture objects + // must be complete and have a base level of |size| dimensions. The color formats do not need + // to match, but |destTexture| must have a renderable format. + virtual void copyTexture(Parameters) = 0; + virtual void flush() = 0; + + virtual ~TextureCopier() { } +}; + +#if USE(ACCELERATED_COMPOSITING) + +class AcceleratedTextureCopier : public TextureCopier { + WTF_MAKE_NONCOPYABLE(AcceleratedTextureCopier); +public: + static PassOwnPtr<AcceleratedTextureCopier> create(WebKit::WebGraphicsContext3D* context, bool usingBindUniforms) + { + return adoptPtr(new AcceleratedTextureCopier(context, usingBindUniforms)); + } + virtual ~AcceleratedTextureCopier(); + + virtual void copyTexture(Parameters) OVERRIDE; + virtual void flush() OVERRIDE; + +protected: + AcceleratedTextureCopier(WebKit::WebGraphicsContext3D*, bool usingBindUniforms); + +private: + typedef ProgramBinding<VertexShaderPosTexIdentity, FragmentShaderRGBATex> BlitProgram; + + WebKit::WebGraphicsContext3D* m_context; + Platform3DObject m_fbo; + Platform3DObject m_positionBuffer; + OwnPtr<BlitProgram> m_blitProgram; + bool m_usingBindUniforms; +}; + +#endif // USE(ACCELERATED_COMPOSITING) + +} + +#endif diff --git a/cc/TextureLayerChromium.cpp b/cc/TextureLayerChromium.cpp new file mode 100644 index 0000000..0ba2e61 --- /dev/null +++ b/cc/TextureLayerChromium.cpp @@ -0,0 +1,133 @@ +// 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" + +#if USE(ACCELERATED_COMPOSITING) + +#include "TextureLayerChromium.h" + +#include "CCLayerTreeHost.h" +#include "CCTextureLayerImpl.h" +#include <public/WebGraphicsContext3D.h> + +namespace WebCore { + +PassRefPtr<TextureLayerChromium> TextureLayerChromium::create(TextureLayerChromiumClient* client) +{ + return adoptRef(new TextureLayerChromium(client)); +} + +TextureLayerChromium::TextureLayerChromium(TextureLayerChromiumClient* client) + : LayerChromium() + , m_client(client) + , m_flipped(true) + , m_uvRect(0, 0, 1, 1) + , m_premultipliedAlpha(true) + , m_rateLimitContext(false) + , m_contextLost(false) + , m_textureId(0) +{ +} + +TextureLayerChromium::~TextureLayerChromium() +{ + if (layerTreeHost()) { + if (m_textureId) + layerTreeHost()->acquireLayerTextures(); + if (m_rateLimitContext && m_client) + layerTreeHost()->stopRateLimiter(m_client->context()); + } +} + +PassOwnPtr<CCLayerImpl> TextureLayerChromium::createCCLayerImpl() +{ + return CCTextureLayerImpl::create(m_layerId); +} + +void TextureLayerChromium::setFlipped(bool flipped) +{ + m_flipped = flipped; + setNeedsCommit(); +} + +void TextureLayerChromium::setUVRect(const FloatRect& rect) +{ + m_uvRect = rect; + setNeedsCommit(); +} + +void TextureLayerChromium::setPremultipliedAlpha(bool premultipliedAlpha) +{ + m_premultipliedAlpha = premultipliedAlpha; + setNeedsCommit(); +} + +void TextureLayerChromium::setRateLimitContext(bool rateLimit) +{ + if (!rateLimit && m_rateLimitContext && m_client && layerTreeHost()) + layerTreeHost()->stopRateLimiter(m_client->context()); + + m_rateLimitContext = rateLimit; +} + +void TextureLayerChromium::setTextureId(unsigned id) +{ + if (m_textureId == id) + return; + if (m_textureId && layerTreeHost()) + layerTreeHost()->acquireLayerTextures(); + m_textureId = id; + setNeedsCommit(); +} + +void TextureLayerChromium::willModifyTexture() +{ + if (layerTreeHost()) + layerTreeHost()->acquireLayerTextures(); +} + +void TextureLayerChromium::setNeedsDisplayRect(const FloatRect& dirtyRect) +{ + LayerChromium::setNeedsDisplayRect(dirtyRect); + + if (m_rateLimitContext && m_client && layerTreeHost()) + layerTreeHost()->startRateLimiter(m_client->context()); +} + +void TextureLayerChromium::setLayerTreeHost(CCLayerTreeHost* host) +{ + if (m_textureId && layerTreeHost() && host != layerTreeHost()) + layerTreeHost()->acquireLayerTextures(); + LayerChromium::setLayerTreeHost(host); +} + +bool TextureLayerChromium::drawsContent() const +{ + return (m_client || m_textureId) && !m_contextLost && LayerChromium::drawsContent(); +} + +void TextureLayerChromium::update(CCTextureUpdateQueue& queue, const CCOcclusionTracker*, CCRenderingStats&) +{ + if (m_client) { + m_textureId = m_client->prepareTexture(queue); + m_contextLost = m_client->context()->getGraphicsResetStatusARB() != GraphicsContext3D::NO_ERROR; + } + + m_needsDisplay = false; +} + +void TextureLayerChromium::pushPropertiesTo(CCLayerImpl* layer) +{ + LayerChromium::pushPropertiesTo(layer); + + CCTextureLayerImpl* textureLayer = static_cast<CCTextureLayerImpl*>(layer); + textureLayer->setFlipped(m_flipped); + textureLayer->setUVRect(m_uvRect); + textureLayer->setPremultipliedAlpha(m_premultipliedAlpha); + textureLayer->setTextureId(m_textureId); +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/TextureLayerChromium.h b/cc/TextureLayerChromium.h new file mode 100644 index 0000000..5728d74 --- /dev/null +++ b/cc/TextureLayerChromium.h @@ -0,0 +1,90 @@ +// 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. + + +#ifndef TextureLayerChromium_h +#define TextureLayerChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "LayerChromium.h" + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace WebCore { + +class TextureLayerChromiumClient { +public: + // Called to prepare this layer's texture for compositing. The client may queue a texture + // upload or copy on the CCTextureUpdateQueue. + // Returns the texture ID to be used for compositing. + virtual unsigned prepareTexture(CCTextureUpdateQueue&) = 0; + + // Returns the context that is providing the texture. Used for rate limiting and detecting lost context. + virtual WebKit::WebGraphicsContext3D* context() = 0; + +protected: + virtual ~TextureLayerChromiumClient() { } +}; + +// A Layer containing a the rendered output of a plugin instance. +class TextureLayerChromium : public LayerChromium { +public: + // If this texture layer requires special preparation logic for each frame driven by + // the compositor, pass in a non-nil client. Pass in a nil client pointer if texture updates + // are driven by an external process. + static PassRefPtr<TextureLayerChromium> create(TextureLayerChromiumClient*); + virtual ~TextureLayerChromium(); + + void clearClient() { m_client = 0; } + + virtual PassOwnPtr<CCLayerImpl> createCCLayerImpl() OVERRIDE; + + // Sets whether this texture should be Y-flipped at draw time. Defaults to true. + void setFlipped(bool); + + // Sets a UV transform to be used at draw time. Defaults to (0, 0, 1, 1). + void setUVRect(const FloatRect&); + + // Sets whether the alpha channel is premultiplied or unpremultiplied. Defaults to true. + void setPremultipliedAlpha(bool); + + // Sets whether this context should rate limit on damage to prevent too many frames from + // being queued up before the compositor gets a chance to run. Requires a non-nil client. + // Defaults to false. + void setRateLimitContext(bool); + + // Code path for plugins which supply their own texture ID. + void setTextureId(unsigned); + + void willModifyTexture(); + + virtual void setNeedsDisplayRect(const FloatRect&) OVERRIDE; + + virtual void setLayerTreeHost(CCLayerTreeHost*) OVERRIDE; + virtual bool drawsContent() const OVERRIDE; + virtual void update(CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&) OVERRIDE; + virtual void pushPropertiesTo(CCLayerImpl*) OVERRIDE; + +protected: + explicit TextureLayerChromium(TextureLayerChromiumClient*); + +private: + TextureLayerChromiumClient* m_client; + + bool m_flipped; + FloatRect m_uvRect; + bool m_premultipliedAlpha; + bool m_rateLimitContext; + bool m_contextLost; + + unsigned m_textureId; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/TextureUploader.h b/cc/TextureUploader.h new file mode 100644 index 0000000..1826fcb --- /dev/null +++ b/cc/TextureUploader.h @@ -0,0 +1,30 @@ +// Copyright 2012 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. + +#ifndef TextureUploader_h +#define TextureUploader_h + +#include "LayerTextureUpdater.h" + +namespace WebCore { + +class TextureUploader { +public: + struct Parameters { + LayerTextureUpdater::Texture* texture; + IntRect sourceRect; + IntSize destOffset; + }; + + virtual ~TextureUploader() { } + + virtual bool isBusy() = 0; + virtual void beginUploads() = 0; + virtual void endUploads() = 0; + virtual void uploadTexture(CCResourceProvider*, Parameters) = 0; +}; + +} + +#endif diff --git a/cc/ThrottledTextureUploader.cpp b/cc/ThrottledTextureUploader.cpp new file mode 100644 index 0000000..594342e --- /dev/null +++ b/cc/ThrottledTextureUploader.cpp @@ -0,0 +1,118 @@ +// Copyright 2012 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 "ThrottledTextureUploader.h" + +#include "Extensions3DChromium.h" +#include <public/WebGraphicsContext3D.h> + +namespace { + +// Number of pending texture update queries to allow. +static const size_t maxPendingQueries = 2; + +} // anonymous namespace + +namespace WebCore { + +ThrottledTextureUploader::Query::Query(WebKit::WebGraphicsContext3D* context) + : m_context(context) + , m_queryId(0) +{ + m_queryId = m_context->createQueryEXT(); +} + +ThrottledTextureUploader::Query::~Query() +{ + m_context->deleteQueryEXT(m_queryId); +} + +void ThrottledTextureUploader::Query::begin() +{ + m_context->beginQueryEXT(Extensions3DChromium::COMMANDS_ISSUED_CHROMIUM, m_queryId); +} + +void ThrottledTextureUploader::Query::end() +{ + m_context->endQueryEXT(Extensions3DChromium::COMMANDS_ISSUED_CHROMIUM); +} + +bool ThrottledTextureUploader::Query::isPending() +{ + unsigned available = 1; + m_context->getQueryObjectuivEXT(m_queryId, Extensions3DChromium::QUERY_RESULT_AVAILABLE_EXT, &available); + return !available; +} + +void ThrottledTextureUploader::Query::wait() +{ + unsigned result; + m_context->getQueryObjectuivEXT(m_queryId, Extensions3DChromium::QUERY_RESULT_EXT, &result); +} + +ThrottledTextureUploader::ThrottledTextureUploader(WebKit::WebGraphicsContext3D* context) + : m_context(context) + , m_maxPendingQueries(maxPendingQueries) +{ +} + +ThrottledTextureUploader::ThrottledTextureUploader(WebKit::WebGraphicsContext3D* context, size_t pendingUploadLimit) + : m_context(context) + , m_maxPendingQueries(pendingUploadLimit) +{ + ASSERT(m_context); +} + +ThrottledTextureUploader::~ThrottledTextureUploader() +{ +} + +bool ThrottledTextureUploader::isBusy() +{ + processQueries(); + + if (!m_availableQueries.isEmpty()) + return false; + + if (m_pendingQueries.size() == m_maxPendingQueries) + return true; + + m_availableQueries.append(Query::create(m_context)); + return false; +} + +void ThrottledTextureUploader::beginUploads() +{ + // Wait for query to become available. + while (isBusy()) + m_pendingQueries.first()->wait(); + + ASSERT(!m_availableQueries.isEmpty()); + m_availableQueries.first()->begin(); +} + +void ThrottledTextureUploader::endUploads() +{ + m_availableQueries.first()->end(); + m_pendingQueries.append(m_availableQueries.takeFirst()); +} + +void ThrottledTextureUploader::uploadTexture(CCResourceProvider* resourceProvider, Parameters upload) +{ + upload.texture->updateRect(resourceProvider, upload.sourceRect, upload.destOffset); +} + +void ThrottledTextureUploader::processQueries() +{ + while (!m_pendingQueries.isEmpty()) { + if (m_pendingQueries.first()->isPending()) + break; + + m_availableQueries.append(m_pendingQueries.takeFirst()); + } +} + +} diff --git a/cc/ThrottledTextureUploader.h b/cc/ThrottledTextureUploader.h new file mode 100644 index 0000000..f51d866 --- /dev/null +++ b/cc/ThrottledTextureUploader.h @@ -0,0 +1,68 @@ +// Copyright 2012 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. + +#ifndef ThrottledTextureUploader_h +#define ThrottledTextureUploader_h + +#include "TextureUploader.h" + +#include <wtf/Deque.h> + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace WebCore { + +class ThrottledTextureUploader : public TextureUploader { + WTF_MAKE_NONCOPYABLE(ThrottledTextureUploader); +public: + static PassOwnPtr<ThrottledTextureUploader> create(WebKit::WebGraphicsContext3D* context) + { + return adoptPtr(new ThrottledTextureUploader(context)); + } + static PassOwnPtr<ThrottledTextureUploader> create(WebKit::WebGraphicsContext3D* context, size_t pendingUploadLimit) + { + return adoptPtr(new ThrottledTextureUploader(context, pendingUploadLimit)); + } + virtual ~ThrottledTextureUploader(); + + virtual bool isBusy() OVERRIDE; + virtual void beginUploads() OVERRIDE; + virtual void endUploads() OVERRIDE; + virtual void uploadTexture(CCResourceProvider*, Parameters) OVERRIDE; + +private: + class Query { + public: + static PassOwnPtr<Query> create(WebKit::WebGraphicsContext3D* context) { return adoptPtr(new Query(context)); } + + virtual ~Query(); + + void begin(); + void end(); + bool isPending(); + void wait(); + + private: + explicit Query(WebKit::WebGraphicsContext3D*); + + WebKit::WebGraphicsContext3D* m_context; + unsigned m_queryId; + }; + + ThrottledTextureUploader(WebKit::WebGraphicsContext3D*); + ThrottledTextureUploader(WebKit::WebGraphicsContext3D*, size_t pendingUploadLimit); + + void processQueries(); + + WebKit::WebGraphicsContext3D* m_context; + size_t m_maxPendingQueries; + Deque<OwnPtr<Query> > m_pendingQueries; + Deque<OwnPtr<Query> > m_availableQueries; +}; + +} + +#endif diff --git a/cc/TiledLayerChromium.cpp b/cc/TiledLayerChromium.cpp new file mode 100644 index 0000000..8a6e4b9 --- /dev/null +++ b/cc/TiledLayerChromium.cpp @@ -0,0 +1,791 @@ +// 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 "config.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "TiledLayerChromium.h" + +#include "CCLayerImpl.h" +#include "CCLayerTreeHost.h" +#include "CCOverdrawMetrics.h" +#include "CCTextureUpdateQueue.h" +#include "CCTiledLayerImpl.h" +#include "GraphicsContext3D.h" +#include "Region.h" +#include "TextStream.h" +#include <wtf/CurrentTime.h> +#include <wtf/MathExtras.h> + +using namespace std; +using WebKit::WebTransformationMatrix; + +namespace WebCore { + +class UpdatableTile : public CCLayerTilingData::Tile { + WTF_MAKE_NONCOPYABLE(UpdatableTile); +public: + static PassOwnPtr<UpdatableTile> create(PassOwnPtr<LayerTextureUpdater::Texture> texture) + { + return adoptPtr(new UpdatableTile(texture)); + } + + LayerTextureUpdater::Texture* texture() { return m_texture.get(); } + CCPrioritizedTexture* managedTexture() { return m_texture->texture(); } + + bool isDirty() const { return !dirtyRect.isEmpty(); } + + // Reset update state for the current frame. This should occur before painting + // for all layers. Since painting one layer can invalidate another layer + // after it has already painted, mark all non-dirty tiles as valid before painting + // such that invalidations during painting won't prevent them from being pushed. + void resetUpdateState() + { + updateRect = IntRect(); + occluded = false; + partialUpdate = false; + validForFrame = !isDirty(); + } + + // This promises to update the tile and therefore also guarantees the tile + // will be valid for this frame. dirtyRect is copied into updateRect so + // we can continue to track re-entrant invalidations that occur during painting. + void markForUpdate() + { + validForFrame = true; + updateRect = dirtyRect; + dirtyRect = IntRect(); + } + + IntRect dirtyRect; + IntRect updateRect; + bool partialUpdate; + bool validForFrame; + bool occluded; + bool isInUseOnImpl; +private: + explicit UpdatableTile(PassOwnPtr<LayerTextureUpdater::Texture> texture) + : partialUpdate(false) + , validForFrame(false) + , occluded(false) + , isInUseOnImpl(false) + , m_texture(texture) + { + } + + OwnPtr<LayerTextureUpdater::Texture> m_texture; +}; + +TiledLayerChromium::TiledLayerChromium() + : LayerChromium() + , m_textureFormat(GraphicsContext3D::INVALID_ENUM) + , m_skipsDraw(false) + , m_failedUpdate(false) + , m_sampledTexelFormat(LayerTextureUpdater::SampledTexelFormatInvalid) + , m_tilingOption(AutoTile) +{ + m_tiler = CCLayerTilingData::create(IntSize(), CCLayerTilingData::HasBorderTexels); +} + +TiledLayerChromium::~TiledLayerChromium() +{ +} + +PassOwnPtr<CCLayerImpl> TiledLayerChromium::createCCLayerImpl() +{ + return CCTiledLayerImpl::create(id()); +} + +void TiledLayerChromium::updateTileSizeAndTilingOption() +{ + ASSERT(layerTreeHost()); + + const IntSize& defaultTileSize = layerTreeHost()->settings().defaultTileSize; + const IntSize& maxUntiledLayerSize = layerTreeHost()->settings().maxUntiledLayerSize; + int layerWidth = contentBounds().width(); + int layerHeight = contentBounds().height(); + + const IntSize tileSize(min(defaultTileSize.width(), layerWidth), min(defaultTileSize.height(), layerHeight)); + + // Tile if both dimensions large, or any one dimension large and the other + // extends into a second tile but the total layer area isn't larger than that + // of the largest possible untiled layer. This heuristic allows for long skinny layers + // (e.g. scrollbars) that are Nx1 tiles to minimize wasted texture space but still avoids + // creating very large tiles. + const bool anyDimensionLarge = layerWidth > maxUntiledLayerSize.width() || layerHeight > maxUntiledLayerSize.height(); + const bool anyDimensionOneTile = (layerWidth <= defaultTileSize.width() || layerHeight <= defaultTileSize.height()) + && (layerWidth * layerHeight) <= (maxUntiledLayerSize.width() * maxUntiledLayerSize.height()); + const bool autoTiled = anyDimensionLarge && !anyDimensionOneTile; + + bool isTiled; + if (m_tilingOption == AlwaysTile) + isTiled = true; + else if (m_tilingOption == NeverTile) + isTiled = false; + else + isTiled = autoTiled; + + IntSize requestedSize = isTiled ? tileSize : contentBounds(); + const int maxSize = layerTreeHost()->rendererCapabilities().maxTextureSize; + IntSize clampedSize = requestedSize.shrunkTo(IntSize(maxSize, maxSize)); + setTileSize(clampedSize); +} + +void TiledLayerChromium::updateBounds() +{ + IntSize oldBounds = m_tiler->bounds(); + IntSize newBounds = contentBounds(); + if (oldBounds == newBounds) + return; + m_tiler->setBounds(newBounds); + + // Invalidate any areas that the new bounds exposes. + Region oldRegion(IntRect(IntPoint(), oldBounds)); + Region newRegion(IntRect(IntPoint(), newBounds)); + newRegion.subtract(oldRegion); + Vector<IntRect> rects = newRegion.rects(); + for (size_t i = 0; i < rects.size(); ++i) + invalidateContentRect(rects[i]); +} + +void TiledLayerChromium::setTileSize(const IntSize& size) +{ + m_tiler->setTileSize(size); +} + +void TiledLayerChromium::setBorderTexelOption(CCLayerTilingData::BorderTexelOption borderTexelOption) +{ + m_tiler->setBorderTexelOption(borderTexelOption); +} + +bool TiledLayerChromium::drawsContent() const +{ + if (!LayerChromium::drawsContent()) + return false; + + bool hasMoreThanOneTile = m_tiler->numTilesX() > 1 || m_tiler->numTilesY() > 1; + if (m_tilingOption == NeverTile && hasMoreThanOneTile) + return false; + + return true; +} + +bool TiledLayerChromium::needsContentsScale() const +{ + return true; +} + +IntSize TiledLayerChromium::contentBounds() const +{ + return IntSize(lroundf(bounds().width() * contentsScale()), lroundf(bounds().height() * contentsScale())); +} + +void TiledLayerChromium::setTilingOption(TilingOption tilingOption) +{ + m_tilingOption = tilingOption; +} + +void TiledLayerChromium::setIsMask(bool isMask) +{ + setTilingOption(isMask ? NeverTile : AutoTile); +} + +void TiledLayerChromium::pushPropertiesTo(CCLayerImpl* layer) +{ + LayerChromium::pushPropertiesTo(layer); + + CCTiledLayerImpl* tiledLayer = static_cast<CCTiledLayerImpl*>(layer); + + tiledLayer->setSkipsDraw(m_skipsDraw); + tiledLayer->setContentsSwizzled(m_sampledTexelFormat != LayerTextureUpdater::SampledTexelFormatRGBA); + tiledLayer->setTilingData(*m_tiler); + Vector<UpdatableTile*> invalidTiles; + + for (CCLayerTilingData::TileMap::const_iterator iter = m_tiler->tiles().begin(); iter != m_tiler->tiles().end(); ++iter) { + int i = iter->first.first; + int j = iter->first.second; + UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second.get()); + // FIXME: This should not ever be null. + if (!tile) + continue; + tile->isInUseOnImpl = false; + if (!tile->managedTexture()->haveBackingTexture()) { + invalidTiles.append(tile); + continue; + } + if (!tile->validForFrame) + continue; + + tiledLayer->pushTileProperties(i, j, tile->managedTexture()->resourceId(), tile->opaqueRect()); + tile->isInUseOnImpl = true; + } + for (Vector<UpdatableTile*>::const_iterator iter = invalidTiles.begin(); iter != invalidTiles.end(); ++iter) + m_tiler->takeTile((*iter)->i(), (*iter)->j()); +} + +CCPrioritizedTextureManager* TiledLayerChromium::textureManager() const +{ + if (!layerTreeHost()) + return 0; + return layerTreeHost()->contentsTextureManager(); +} + +void TiledLayerChromium::setLayerTreeHost(CCLayerTreeHost* host) +{ + if (host && host != layerTreeHost()) { + for (CCLayerTilingData::TileMap::const_iterator iter = m_tiler->tiles().begin(); iter != m_tiler->tiles().end(); ++iter) { + UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second.get()); + // FIXME: This should not ever be null. + if (!tile) + continue; + tile->managedTexture()->setTextureManager(host->contentsTextureManager()); + } + } + LayerChromium::setLayerTreeHost(host); +} + +UpdatableTile* TiledLayerChromium::tileAt(int i, int j) const +{ + return static_cast<UpdatableTile*>(m_tiler->tileAt(i, j)); +} + +UpdatableTile* TiledLayerChromium::createTile(int i, int j) +{ + createTextureUpdaterIfNeeded(); + + OwnPtr<UpdatableTile> tile(UpdatableTile::create(textureUpdater()->createTexture(textureManager()))); + tile->managedTexture()->setDimensions(m_tiler->tileSize(), m_textureFormat); + + UpdatableTile* addedTile = tile.get(); + m_tiler->addTile(tile.release(), i, j); + + addedTile->dirtyRect = m_tiler->tileRect(addedTile); + + // Temporary diagnostic crash. + if (!addedTile) + CRASH(); + if (!tileAt(i, j)) + CRASH(); + + return addedTile; +} + +void TiledLayerChromium::setNeedsDisplayRect(const FloatRect& dirtyRect) +{ + float contentsWidthScale = static_cast<float>(contentBounds().width()) / bounds().width(); + float contentsHeightScale = static_cast<float>(contentBounds().height()) / bounds().height(); + FloatRect scaledDirtyRect(dirtyRect); + scaledDirtyRect.scale(contentsWidthScale, contentsHeightScale); + IntRect dirty = enclosingIntRect(scaledDirtyRect); + invalidateContentRect(dirty); + LayerChromium::setNeedsDisplayRect(dirtyRect); +} + +void TiledLayerChromium::setUseLCDText(bool useLCDText) +{ + LayerChromium::setUseLCDText(useLCDText); + + CCLayerTilingData::BorderTexelOption borderTexelOption; +#if OS(ANDROID) + // Always want border texels and GL_LINEAR due to pinch zoom. + borderTexelOption = CCLayerTilingData::HasBorderTexels; +#else + borderTexelOption = useLCDText ? CCLayerTilingData::NoBorderTexels : CCLayerTilingData::HasBorderTexels; +#endif + setBorderTexelOption(borderTexelOption); +} + +void TiledLayerChromium::invalidateContentRect(const IntRect& contentRect) +{ + updateBounds(); + if (m_tiler->isEmpty() || contentRect.isEmpty() || m_skipsDraw) + return; + + for (CCLayerTilingData::TileMap::const_iterator iter = m_tiler->tiles().begin(); iter != m_tiler->tiles().end(); ++iter) { + UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second.get()); + ASSERT(tile); + // FIXME: This should not ever be null. + if (!tile) + continue; + IntRect bound = m_tiler->tileRect(tile); + bound.intersect(contentRect); + tile->dirtyRect.unite(bound); + } +} + +// Returns true if tile is dirty and only part of it needs to be updated. +bool TiledLayerChromium::tileOnlyNeedsPartialUpdate(UpdatableTile* tile) +{ + return !tile->dirtyRect.contains(m_tiler->tileRect(tile)); +} + +// Dirty tiles with valid textures needs buffered update to guarantee that +// we don't modify textures currently used for drawing by the impl thread. +bool TiledLayerChromium::tileNeedsBufferedUpdate(UpdatableTile* tile) +{ + if (!tile->managedTexture()->haveBackingTexture()) + return false; + + if (!tile->isDirty()) + return false; + + if (!tile->isInUseOnImpl) + return false; + + return true; +} + + +bool TiledLayerChromium::updateTiles(int left, int top, int right, int bottom, CCTextureUpdateQueue& queue, const CCOcclusionTracker* occlusion, CCRenderingStats& stats, bool& didPaint) +{ + didPaint = false; + createTextureUpdaterIfNeeded(); + + bool ignoreOcclusions = !occlusion; + if (!haveTexturesForTiles(left, top, right, bottom, ignoreOcclusions)) { + m_failedUpdate = true; + return false; + } + + IntRect paintRect = markTilesForUpdate(left, top, right, bottom, ignoreOcclusions); + + if (occlusion) + occlusion->overdrawMetrics().didPaint(paintRect); + + if (paintRect.isEmpty()) + return true; + + didPaint = true; + updateTileTextures(paintRect, left, top, right, bottom, queue, occlusion, stats); + return true; +} + +void TiledLayerChromium::markOcclusionsAndRequestTextures(int left, int top, int right, int bottom, const CCOcclusionTracker* occlusion) +{ + // There is some difficult dependancies between occlusions, recording occlusion metrics + // and requesting memory so those are encapsulated in this function: + // - We only want to call requestLate on unoccluded textures (to preserve + // memory for other layers when near OOM). + // - We only want to record occlusion metrics if all memory requests succeed. + + int occludedTileCount = 0; + bool succeeded = true; + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = tileAt(i, j); + ASSERT(tile); // Did setTexturePriorities get skipped? + // FIXME: This should not ever be null. + if (!tile) + continue; + ASSERT(!tile->occluded); // Did resetUpdateState get skipped? Are we doing more than one occlusion pass? + IntRect visibleTileRect = intersection(m_tiler->tileBounds(i, j), visibleContentRect()); + if (occlusion && occlusion->occluded(this, visibleTileRect)) { + tile->occluded = true; + occludedTileCount++; + } else { + succeeded &= tile->managedTexture()->requestLate(); + } + } + } + + if (!succeeded) + return; + + // FIXME: Remove the loop and just pass the count! + for (int i = 0; i < occludedTileCount; i++) + occlusion->overdrawMetrics().didCullTileForUpload(); +} + +bool TiledLayerChromium::haveTexturesForTiles(int left, int top, int right, int bottom, bool ignoreOcclusions) +{ + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = tileAt(i, j); + ASSERT(tile); // Did setTexturePriorites get skipped? + // FIXME: This should not ever be null. + if (!tile) + continue; + + // Ensure the entire tile is dirty if we don't have the texture. + if (!tile->managedTexture()->haveBackingTexture()) + tile->dirtyRect = m_tiler->tileRect(tile); + + // If using occlusion and the visible region of the tile is occluded, + // don't reserve a texture or update the tile. + if (tile->occluded && !ignoreOcclusions) + continue; + + if (!tile->managedTexture()->canAcquireBackingTexture()) + return false; + } + } + return true; +} + +IntRect TiledLayerChromium::markTilesForUpdate(int left, int top, int right, int bottom, bool ignoreOcclusions) +{ + IntRect paintRect; + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = tileAt(i, j); + ASSERT(tile); // Did setTexturePriorites get skipped? + // FIXME: This should not ever be null. + if (!tile) + continue; + if (tile->occluded && !ignoreOcclusions) + continue; + paintRect.unite(tile->dirtyRect); + tile->markForUpdate(); + } + } + return paintRect; +} + +void TiledLayerChromium::updateTileTextures(const IntRect& paintRect, int left, int top, int right, int bottom, CCTextureUpdateQueue& queue, const CCOcclusionTracker* occlusion, CCRenderingStats& stats) +{ + // The updateRect should be in layer space. So we have to convert the paintRect from content space to layer space. + m_updateRect = FloatRect(paintRect); + float widthScale = bounds().width() / static_cast<float>(contentBounds().width()); + float heightScale = bounds().height() / static_cast<float>(contentBounds().height()); + m_updateRect.scale(widthScale, heightScale); + + // Calling prepareToUpdate() calls into WebKit to paint, which may have the side + // effect of disabling compositing, which causes our reference to the texture updater to be deleted. + // However, we can't free the memory backing the SkCanvas until the paint finishes, + // so we grab a local reference here to hold the updater alive until the paint completes. + RefPtr<LayerTextureUpdater> protector(textureUpdater()); + IntRect paintedOpaqueRect; + textureUpdater()->prepareToUpdate(paintRect, m_tiler->tileSize(), 1 / widthScale, 1 / heightScale, paintedOpaqueRect, stats); + + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = tileAt(i, j); + ASSERT(tile); // Did setTexturePriorites get skipped? + // FIXME: This should not ever be null. + if (!tile) + continue; + + IntRect tileRect = m_tiler->tileBounds(i, j); + + // Use updateRect as the above loop copied the dirty rect for this frame to updateRect. + const IntRect& dirtyRect = tile->updateRect; + if (dirtyRect.isEmpty()) + continue; + + // Save what was painted opaque in the tile. Keep the old area if the paint didn't touch it, and didn't paint some + // other part of the tile opaque. + IntRect tilePaintedRect = intersection(tileRect, paintRect); + IntRect tilePaintedOpaqueRect = intersection(tileRect, paintedOpaqueRect); + if (!tilePaintedRect.isEmpty()) { + IntRect paintInsideTileOpaqueRect = intersection(tile->opaqueRect(), tilePaintedRect); + bool paintInsideTileOpaqueRectIsNonOpaque = !tilePaintedOpaqueRect.contains(paintInsideTileOpaqueRect); + bool opaquePaintNotInsideTileOpaqueRect = !tilePaintedOpaqueRect.isEmpty() && !tile->opaqueRect().contains(tilePaintedOpaqueRect); + + if (paintInsideTileOpaqueRectIsNonOpaque || opaquePaintNotInsideTileOpaqueRect) + tile->setOpaqueRect(tilePaintedOpaqueRect); + } + + // sourceRect starts as a full-sized tile with border texels included. + IntRect sourceRect = m_tiler->tileRect(tile); + sourceRect.intersect(dirtyRect); + // Paint rect not guaranteed to line up on tile boundaries, so + // make sure that sourceRect doesn't extend outside of it. + sourceRect.intersect(paintRect); + + tile->updateRect = sourceRect; + + if (sourceRect.isEmpty()) + continue; + + tile->texture()->prepareRect(sourceRect, stats); + if (occlusion) + occlusion->overdrawMetrics().didUpload(WebTransformationMatrix(), sourceRect, tile->opaqueRect()); + + const IntPoint anchor = m_tiler->tileRect(tile).location(); + + // Calculate tile-space rectangle to upload into. + IntSize destOffset(sourceRect.x() - anchor.x(), sourceRect.y() - anchor.y()); + if (destOffset.width() < 0) + CRASH(); + if (destOffset.height() < 0) + CRASH(); + + // Offset from paint rectangle to this tile's dirty rectangle. + IntPoint paintOffset(sourceRect.x() - paintRect.x(), sourceRect.y() - paintRect.y()); + if (paintOffset.x() < 0) + CRASH(); + if (paintOffset.y() < 0) + CRASH(); + if (paintOffset.x() + sourceRect.width() > paintRect.width()) + CRASH(); + if (paintOffset.y() + sourceRect.height() > paintRect.height()) + CRASH(); + + TextureUploader::Parameters upload = { tile->texture(), sourceRect, destOffset }; + if (tile->partialUpdate) + queue.appendPartialUpload(upload); + else + queue.appendFullUpload(upload); + } + } +} + +namespace { +// This picks a small animated layer to be anything less than one viewport. This +// is specifically for page transitions which are viewport-sized layers. The extra +// 64 pixels is due to these layers being slightly larger than the viewport in some cases. +bool isSmallAnimatedLayer(TiledLayerChromium* layer) +{ + if (!layer->drawTransformIsAnimating() && !layer->screenSpaceTransformIsAnimating()) + return false; + IntSize viewportSize = layer->layerTreeHost() ? layer->layerTreeHost()->deviceViewportSize() : IntSize(); + IntRect contentRect(IntPoint::zero(), layer->contentBounds()); + return contentRect.width() <= viewportSize.width() + 64 + && contentRect.height() <= viewportSize.height() + 64; +} + +// FIXME: Remove this and make this based on distance once distance can be calculated +// for offscreen layers. For now, prioritize all small animated layers after 512 +// pixels of pre-painting. +void setPriorityForTexture(const CCPriorityCalculator& priorityCalc, + const IntRect& visibleRect, + const IntRect& tileRect, + bool drawsToRoot, + bool isSmallAnimatedLayer, + CCPrioritizedTexture* texture) +{ + int priority = CCPriorityCalculator::lowestPriority(); + if (!visibleRect.isEmpty()) + priority = priorityCalc.priorityFromDistance(visibleRect, tileRect, drawsToRoot); + if (isSmallAnimatedLayer) + priority = CCPriorityCalculator::maxPriority(priority, priorityCalc.priorityFromDistance(512, drawsToRoot)); + if (priority != CCPriorityCalculator::lowestPriority()) + texture->setRequestPriority(priority); +} +} + +void TiledLayerChromium::setTexturePriorities(const CCPriorityCalculator& priorityCalc) +{ + updateBounds(); + resetUpdateState(); + + if (m_tiler->hasEmptyBounds()) + return; + + bool drawsToRoot = !renderTarget()->parent(); + bool smallAnimatedLayer = isSmallAnimatedLayer(this); + + // Minimally create the tiles in the desired pre-paint rect. + IntRect createTilesRect = idlePaintRect(); + if (!createTilesRect.isEmpty()) { + int left, top, right, bottom; + m_tiler->contentRectToTileIndices(createTilesRect, left, top, right, bottom); + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + if (!tileAt(i, j)) + createTile(i, j); + } + } + } + + // Also, minimally create all tiles for small animated layers and also + // double-buffer them since we have limited their size to be reasonable. + IntRect doubleBufferedRect = visibleContentRect(); + if (smallAnimatedLayer) + doubleBufferedRect = IntRect(IntPoint::zero(), contentBounds()); + + // Create additional textures for double-buffered updates when needed. + // These textures must stay alive while the updated textures are incrementally + // uploaded, swapped atomically via pushProperties, and finally deleted + // after the commit is complete, after which they can be recycled. + if (!doubleBufferedRect.isEmpty()) { + int left, top, right, bottom; + m_tiler->contentRectToTileIndices(doubleBufferedRect, left, top, right, bottom); + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = tileAt(i, j); + if (!tile) + tile = createTile(i, j); + // We need an additional texture if the tile needs a buffered-update and it's not a partial update. + // FIXME: Decide if partial update should be allowed based on cost + // of update. https://bugs.webkit.org/show_bug.cgi?id=77376 + if (!layerTreeHost() || !layerTreeHost()->bufferedUpdates() || !tileNeedsBufferedUpdate(tile)) + continue; + if (tileOnlyNeedsPartialUpdate(tile) && layerTreeHost()->requestPartialTextureUpdate()) { + tile->partialUpdate = true; + continue; + } + + IntRect tileRect = m_tiler->tileRect(tile); + tile->dirtyRect = tileRect; + LayerTextureUpdater::Texture* backBuffer = tile->texture(); + setPriorityForTexture(priorityCalc, visibleContentRect(), tile->dirtyRect, drawsToRoot, smallAnimatedLayer, backBuffer->texture()); + OwnPtr<CCPrioritizedTexture> frontBuffer = CCPrioritizedTexture::create(backBuffer->texture()->textureManager(), + backBuffer->texture()->size(), + backBuffer->texture()->format()); + // Swap backBuffer into frontBuffer and add it to delete after commit queue. + backBuffer->swapTextureWith(frontBuffer); + layerTreeHost()->deleteTextureAfterCommit(frontBuffer.release()); + } + } + } + + // Now update priorities on all tiles we have in the layer, no matter where they are. + for (CCLayerTilingData::TileMap::const_iterator iter = m_tiler->tiles().begin(); iter != m_tiler->tiles().end(); ++iter) { + UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second.get()); + // FIXME: This should not ever be null. + if (!tile) + continue; + IntRect tileRect = m_tiler->tileRect(tile); + setPriorityForTexture(priorityCalc, visibleContentRect(), tileRect, drawsToRoot, smallAnimatedLayer, tile->managedTexture()); + } +} + +Region TiledLayerChromium::visibleContentOpaqueRegion() const +{ + if (m_skipsDraw) + return Region(); + if (opaque()) + return visibleContentRect(); + return m_tiler->opaqueRegionInContentRect(visibleContentRect()); +} + +void TiledLayerChromium::resetUpdateState() +{ + m_skipsDraw = false; + m_failedUpdate = false; + + CCLayerTilingData::TileMap::const_iterator end = m_tiler->tiles().end(); + for (CCLayerTilingData::TileMap::const_iterator iter = m_tiler->tiles().begin(); iter != end; ++iter) { + UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second.get()); + // FIXME: This should not ever be null. + if (!tile) + continue; + tile->resetUpdateState(); + } +} + +void TiledLayerChromium::update(CCTextureUpdateQueue& queue, const CCOcclusionTracker* occlusion, CCRenderingStats& stats) +{ + ASSERT(!m_skipsDraw && !m_failedUpdate); // Did resetUpdateState get skipped? + updateBounds(); + if (m_tiler->hasEmptyBounds() || !drawsContent()) + return; + + bool didPaint = false; + + // Animation pre-paint. If the layer is small, try to paint it all + // immediately whether or not it is occluded, to avoid paint/upload + // hiccups while it is animating. + if (isSmallAnimatedLayer(this)) { + int left, top, right, bottom; + m_tiler->contentRectToTileIndices(IntRect(IntPoint::zero(), contentBounds()), left, top, right, bottom); + updateTiles(left, top, right, bottom, queue, 0, stats, didPaint); + if (didPaint) + return; + // This was an attempt to paint the entire layer so if we fail it's okay, + // just fallback on painting visible etc. below. + m_failedUpdate = false; + } + + if (visibleContentRect().isEmpty()) + return; + + // Visible painting. First occlude visible tiles and paint the non-occluded tiles. + int left, top, right, bottom; + m_tiler->contentRectToTileIndices(visibleContentRect(), left, top, right, bottom); + markOcclusionsAndRequestTextures(left, top, right, bottom, occlusion); + m_skipsDraw = !updateTiles(left, top, right, bottom, queue, occlusion, stats, didPaint); + if (m_skipsDraw) + m_tiler->reset(); + if (m_skipsDraw || didPaint) + return; + + // If we have already painting everything visible. Do some pre-painting while idle. + IntRect idlePaintContentRect = idlePaintRect(); + if (idlePaintContentRect.isEmpty()) + return; + + // Prepaint anything that was occluded but inside the layer's visible region. + if (!updateTiles(left, top, right, bottom, queue, 0, stats, didPaint) || didPaint) + return; + + int prepaintLeft, prepaintTop, prepaintRight, prepaintBottom; + m_tiler->contentRectToTileIndices(idlePaintContentRect, prepaintLeft, prepaintTop, prepaintRight, prepaintBottom); + + // Then expand outwards from the visible area until we find a dirty row or column to update. + while (left > prepaintLeft || top > prepaintTop || right < prepaintRight || bottom < prepaintBottom) { + if (bottom < prepaintBottom) { + ++bottom; + if (!updateTiles(left, bottom, right, bottom, queue, 0, stats, didPaint) || didPaint) + return; + } + if (top > prepaintTop) { + --top; + if (!updateTiles(left, top, right, top, queue, 0, stats, didPaint) || didPaint) + return; + } + if (left > prepaintLeft) { + --left; + if (!updateTiles(left, top, left, bottom, queue, 0, stats, didPaint) || didPaint) + return; + } + if (right < prepaintRight) { + ++right; + if (!updateTiles(right, top, right, bottom, queue, 0, stats, didPaint) || didPaint) + return; + } + } +} + +bool TiledLayerChromium::needsIdlePaint() +{ + // Don't trigger more paints if we failed (as we'll just fail again). + if (m_failedUpdate || visibleContentRect().isEmpty() || m_tiler->hasEmptyBounds() || !drawsContent()) + return false; + + IntRect idlePaintContentRect = idlePaintRect(); + if (idlePaintContentRect.isEmpty()) + return false; + + int left, top, right, bottom; + m_tiler->contentRectToTileIndices(idlePaintContentRect, left, top, right, bottom); + + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = tileAt(i, j); + ASSERT(tile); // Did setTexturePriorities get skipped? + if (!tile) + continue; + + bool updated = !tile->updateRect.isEmpty(); + bool canAcquire = tile->managedTexture()->canAcquireBackingTexture(); + bool dirty = tile->isDirty() || !tile->managedTexture()->haveBackingTexture(); + if (!updated && canAcquire && dirty) + return true; + } + } + return false; +} + +IntRect TiledLayerChromium::idlePaintRect() +{ + // Don't inflate an empty rect. + if (visibleContentRect().isEmpty()) + return IntRect(); + + // FIXME: This can be made a lot larger now! We should increase + // this slowly while insuring it doesn't cause any perf issues. + IntRect prepaintRect = visibleContentRect(); + prepaintRect.inflateX(m_tiler->tileSize().width()); + prepaintRect.inflateY(m_tiler->tileSize().height() * 2); + IntRect contentRect(IntPoint::zero(), contentBounds()); + prepaintRect.intersect(contentRect); + + return prepaintRect; +} + +} +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/TiledLayerChromium.h b/cc/TiledLayerChromium.h new file mode 100644 index 0000000..c7f57f0 --- /dev/null +++ b/cc/TiledLayerChromium.h @@ -0,0 +1,106 @@ +// 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. + +#ifndef TiledLayerChromium_h +#define TiledLayerChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "CCLayerTilingData.h" +#include "LayerChromium.h" +#include "LayerTextureUpdater.h" + +namespace WebCore { +class UpdatableTile; + +class TiledLayerChromium : public LayerChromium { +public: + enum TilingOption { AlwaysTile, NeverTile, AutoTile }; + + virtual ~TiledLayerChromium(); + + virtual void setIsMask(bool) OVERRIDE; + + virtual void pushPropertiesTo(CCLayerImpl*) OVERRIDE; + + virtual bool drawsContent() const OVERRIDE; + virtual bool needsContentsScale() const OVERRIDE; + + virtual IntSize contentBounds() const OVERRIDE; + + virtual void setNeedsDisplayRect(const FloatRect&) OVERRIDE; + + virtual void setUseLCDText(bool) OVERRIDE; + + virtual void setLayerTreeHost(CCLayerTreeHost*) OVERRIDE; + + virtual void setTexturePriorities(const CCPriorityCalculator&) OVERRIDE; + + virtual Region visibleContentOpaqueRegion() const OVERRIDE; + + virtual void update(CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&) OVERRIDE; + +protected: + TiledLayerChromium(); + + void updateTileSizeAndTilingOption(); + void updateBounds(); + + // Exposed to subclasses for testing. + void setTileSize(const IntSize&); + void setTextureFormat(GC3Denum textureFormat) { m_textureFormat = textureFormat; } + void setBorderTexelOption(CCLayerTilingData::BorderTexelOption); + void setSampledTexelFormat(LayerTextureUpdater::SampledTexelFormat sampledTexelFormat) { m_sampledTexelFormat = sampledTexelFormat; } + size_t numPaintedTiles() { return m_tiler->tiles().size(); } + + virtual LayerTextureUpdater* textureUpdater() const = 0; + virtual void createTextureUpdaterIfNeeded() = 0; + + // Set invalidations to be potentially repainted during update(). + void invalidateContentRect(const IntRect& contentRect); + + // Reset state on tiles that will be used for updating the layer. + void resetUpdateState(); + + // After preparing an update, returns true if more painting is needed. + bool needsIdlePaint(); + IntRect idlePaintRect(); + + bool skipsDraw() const { return m_skipsDraw; } + + // Virtual for testing + virtual CCPrioritizedTextureManager* textureManager() const; + +private: + virtual PassOwnPtr<CCLayerImpl> createCCLayerImpl() OVERRIDE; + + void createTilerIfNeeded(); + void setTilingOption(TilingOption); + + bool tileOnlyNeedsPartialUpdate(UpdatableTile*); + bool tileNeedsBufferedUpdate(UpdatableTile*); + + void markOcclusionsAndRequestTextures(int left, int top, int right, int bottom, const CCOcclusionTracker*); + + bool updateTiles(int left, int top, int right, int bottom, CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&, bool& didPaint); + bool haveTexturesForTiles(int left, int top, int right, int bottom, bool ignoreOcclusions); + IntRect markTilesForUpdate(int left, int top, int right, int bottom, bool ignoreOcclusions); + void updateTileTextures(const IntRect& paintRect, int left, int top, int right, int bottom, CCTextureUpdateQueue&, const CCOcclusionTracker*, CCRenderingStats&); + + UpdatableTile* tileAt(int, int) const; + UpdatableTile* createTile(int, int); + + GC3Denum m_textureFormat; + bool m_skipsDraw; + bool m_failedUpdate; + LayerTextureUpdater::SampledTexelFormat m_sampledTexelFormat; + + TilingOption m_tilingOption; + OwnPtr<CCLayerTilingData> m_tiler; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif diff --git a/cc/TreeSynchronizer.cpp b/cc/TreeSynchronizer.cpp new file mode 100644 index 0000000..29e676c --- /dev/null +++ b/cc/TreeSynchronizer.cpp @@ -0,0 +1,112 @@ +// 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 "config.h" + +#include "TreeSynchronizer.h" + +#include "CCLayerImpl.h" +#include "CCScrollbarAnimationController.h" +#include "CCScrollbarLayerImpl.h" +#include "LayerChromium.h" +#include "ScrollbarLayerChromium.h" +#include <wtf/RefPtr.h> + +namespace WebCore { + +PassOwnPtr<CCLayerImpl> TreeSynchronizer::synchronizeTrees(LayerChromium* layerChromiumRoot, PassOwnPtr<CCLayerImpl> oldCCLayerImplRoot, CCLayerTreeHostImpl* hostImpl) +{ + OwnPtrCCLayerImplMap oldLayers; + RawPtrCCLayerImplMap newLayers; + + collectExistingCCLayerImplRecursive(oldLayers, oldCCLayerImplRoot); + + OwnPtr<CCLayerImpl> newTree = synchronizeTreeRecursive(newLayers, oldLayers, layerChromiumRoot, hostImpl); + + updateScrollbarLayerPointersRecursive(newLayers, layerChromiumRoot); + + return newTree.release(); +} + +void TreeSynchronizer::collectExistingCCLayerImplRecursive(OwnPtrCCLayerImplMap& oldLayers, PassOwnPtr<CCLayerImpl> popCCLayerImpl) +{ + OwnPtr<CCLayerImpl> ccLayerImpl = popCCLayerImpl; + + if (!ccLayerImpl) + return; + + Vector<OwnPtr<CCLayerImpl> >& children = ccLayerImpl->m_children; + for (size_t i = 0; i < children.size(); ++i) + collectExistingCCLayerImplRecursive(oldLayers, children[i].release()); + + collectExistingCCLayerImplRecursive(oldLayers, ccLayerImpl->m_maskLayer.release()); + collectExistingCCLayerImplRecursive(oldLayers, ccLayerImpl->m_replicaLayer.release()); + + int id = ccLayerImpl->id(); + oldLayers.set(id, ccLayerImpl.release()); +} + +PassOwnPtr<CCLayerImpl> TreeSynchronizer::reuseOrCreateCCLayerImpl(RawPtrCCLayerImplMap& newLayers, OwnPtrCCLayerImplMap& oldLayers, LayerChromium* layer) +{ + OwnPtr<CCLayerImpl> ccLayerImpl = oldLayers.take(layer->id()); + + if (!ccLayerImpl) + ccLayerImpl = layer->createCCLayerImpl(); + + newLayers.set(layer->id(), ccLayerImpl.get()); + return ccLayerImpl.release(); +} + +PassOwnPtr<CCLayerImpl> TreeSynchronizer::synchronizeTreeRecursive(RawPtrCCLayerImplMap& newLayers, OwnPtrCCLayerImplMap& oldLayers, LayerChromium* layer, CCLayerTreeHostImpl* hostImpl) +{ + if (!layer) + return nullptr; + + OwnPtr<CCLayerImpl> ccLayerImpl = reuseOrCreateCCLayerImpl(newLayers, oldLayers, layer); + + ccLayerImpl->clearChildList(); + const Vector<RefPtr<LayerChromium> >& children = layer->children(); + for (size_t i = 0; i < children.size(); ++i) + ccLayerImpl->addChild(synchronizeTreeRecursive(newLayers, oldLayers, children[i].get(), hostImpl)); + + ccLayerImpl->setMaskLayer(synchronizeTreeRecursive(newLayers, oldLayers, layer->maskLayer(), hostImpl)); + ccLayerImpl->setReplicaLayer(synchronizeTreeRecursive(newLayers, oldLayers, layer->replicaLayer(), hostImpl)); + + layer->pushPropertiesTo(ccLayerImpl.get()); + ccLayerImpl->setLayerTreeHostImpl(hostImpl); + + // Remove all dangling pointers. The pointers will be setup later in updateScrollbarLayerPointersRecursive phase + if (CCScrollbarAnimationController* scrollbarController = ccLayerImpl->scrollbarAnimationController()) { + scrollbarController->setHorizontalScrollbarLayer(0); + scrollbarController->setVerticalScrollbarLayer(0); + } + + return ccLayerImpl.release(); +} + +void TreeSynchronizer::updateScrollbarLayerPointersRecursive(const RawPtrCCLayerImplMap& newLayers, LayerChromium* layer) +{ + if (!layer) + return; + + const Vector<RefPtr<LayerChromium> >& children = layer->children(); + for (size_t i = 0; i < children.size(); ++i) + updateScrollbarLayerPointersRecursive(newLayers, children[i].get()); + + ScrollbarLayerChromium* scrollbarLayer = layer->toScrollbarLayerChromium(); + if (!scrollbarLayer) + return; + + CCScrollbarLayerImpl* ccScrollbarLayerImpl = static_cast<CCScrollbarLayerImpl*>(newLayers.get(scrollbarLayer->id())); + ASSERT(ccScrollbarLayerImpl); + CCLayerImpl* ccScrollLayerImpl = newLayers.get(scrollbarLayer->scrollLayerId()); + ASSERT(ccScrollLayerImpl); + + if (ccScrollbarLayerImpl->orientation() == WebKit::WebScrollbar::Horizontal) + ccScrollLayerImpl->setHorizontalScrollbarLayer(ccScrollbarLayerImpl); + else + ccScrollLayerImpl->setVerticalScrollbarLayer(ccScrollbarLayerImpl); +} + +} // namespace WebCore diff --git a/cc/TreeSynchronizer.h b/cc/TreeSynchronizer.h new file mode 100644 index 0000000..5c59e5b --- /dev/null +++ b/cc/TreeSynchronizer.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef TreeSynchronizer_h +#define TreeSynchronizer_h + +#include <wtf/HashMap.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class CCLayerImpl; +class CCLayerTreeHostImpl; +class LayerChromium; + +class TreeSynchronizer { +WTF_MAKE_NONCOPYABLE(TreeSynchronizer); +public: + // Accepts a LayerChromium tree and returns a reference to a CCLayerImpl tree that duplicates the structure + // of the LayerChromium tree, reusing the CCLayerImpls in the tree provided by oldCCLayerImplRoot if possible. + static PassOwnPtr<CCLayerImpl> synchronizeTrees(LayerChromium* layerRoot, PassOwnPtr<CCLayerImpl> oldCCLayerImplRoot, CCLayerTreeHostImpl*); + +private: + TreeSynchronizer(); // Not instantiable. + + typedef HashMap<int, OwnPtr<CCLayerImpl> > OwnPtrCCLayerImplMap; + typedef HashMap<int, CCLayerImpl*> RawPtrCCLayerImplMap; + + // Declared as static member functions so they can access functions on LayerChromium as a friend class. + static PassOwnPtr<CCLayerImpl> reuseOrCreateCCLayerImpl(RawPtrCCLayerImplMap& newLayers, OwnPtrCCLayerImplMap& oldLayers, LayerChromium*); + static void collectExistingCCLayerImplRecursive(OwnPtrCCLayerImplMap& oldLayers, PassOwnPtr<CCLayerImpl>); + static PassOwnPtr<CCLayerImpl> synchronizeTreeRecursive(RawPtrCCLayerImplMap& newLayers, OwnPtrCCLayerImplMap& oldLayers, LayerChromium*, CCLayerTreeHostImpl*); + static void updateScrollbarLayerPointersRecursive(const RawPtrCCLayerImplMap& newLayers, LayerChromium*); +}; + +} // namespace WebCore + +#endif // TreeSynchronizer_h diff --git a/cc/UnthrottledTextureUploader.h b/cc/UnthrottledTextureUploader.h new file mode 100644 index 0000000..36a6cb4 --- /dev/null +++ b/cc/UnthrottledTextureUploader.h @@ -0,0 +1,33 @@ +// Copyright 2012 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. + +#ifndef UnthrottledTextureUploader_h +#define UnthrottledTextureUploader_h + +#include "CCResourceProvider.h" +#include "TextureUploader.h" + +namespace WebCore { + +class UnthrottledTextureUploader : public TextureUploader { + WTF_MAKE_NONCOPYABLE(UnthrottledTextureUploader); +public: + static PassOwnPtr<UnthrottledTextureUploader> create() + { + return adoptPtr(new UnthrottledTextureUploader()); + } + virtual ~UnthrottledTextureUploader() { } + + virtual bool isBusy() OVERRIDE { return false; } + virtual void beginUploads() OVERRIDE { } + virtual void endUploads() OVERRIDE { } + virtual void uploadTexture(CCResourceProvider* resourceProvider, Parameters upload) OVERRIDE { upload.texture->updateRect(resourceProvider, upload.sourceRect, upload.destOffset); } + +protected: + UnthrottledTextureUploader() { } +}; + +} + +#endif diff --git a/cc/VideoLayerChromium.cpp b/cc/VideoLayerChromium.cpp new file mode 100644 index 0000000..7bfee0c --- /dev/null +++ b/cc/VideoLayerChromium.cpp @@ -0,0 +1,37 @@ +// 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" + +#if USE(ACCELERATED_COMPOSITING) +#include "VideoLayerChromium.h" + +#include "CCVideoLayerImpl.h" + +namespace WebCore { + +PassRefPtr<VideoLayerChromium> VideoLayerChromium::create(WebKit::WebVideoFrameProvider* provider) +{ + return adoptRef(new VideoLayerChromium(provider)); +} + +VideoLayerChromium::VideoLayerChromium(WebKit::WebVideoFrameProvider* provider) + : LayerChromium() + , m_provider(provider) +{ + ASSERT(m_provider); +} + +VideoLayerChromium::~VideoLayerChromium() +{ +} + +PassOwnPtr<CCLayerImpl> VideoLayerChromium::createCCLayerImpl() +{ + return CCVideoLayerImpl::create(m_layerId, m_provider); +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) diff --git a/cc/VideoLayerChromium.h b/cc/VideoLayerChromium.h new file mode 100644 index 0000000..88671d0 --- /dev/null +++ b/cc/VideoLayerChromium.h @@ -0,0 +1,40 @@ +// 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. + + +#ifndef VideoLayerChromium_h +#define VideoLayerChromium_h + +#if USE(ACCELERATED_COMPOSITING) + +#include "LayerChromium.h" + +namespace WebKit { +class WebVideoFrameProvider; +} + +namespace WebCore { + +class CCVideoLayerImpl; + +// A Layer that contains a Video element. +class VideoLayerChromium : public LayerChromium { +public: + + static PassRefPtr<VideoLayerChromium> create(WebKit::WebVideoFrameProvider*); + virtual ~VideoLayerChromium(); + + virtual PassOwnPtr<CCLayerImpl> createCCLayerImpl() OVERRIDE; + +private: + explicit VideoLayerChromium(WebKit::WebVideoFrameProvider*); + + // This pointer is only for passing to CCVideoLayerImpl's constructor. It should never be dereferenced by this class. + WebKit::WebVideoFrameProvider* m_provider; +}; + +} +#endif // USE(ACCELERATED_COMPOSITING) + +#endif @@ -5,12 +5,247 @@ { 'variables': { 'chromium_code': 0, + 'use_libcc_for_compositor%': 0, + 'cc_source_files': [ + 'BitmapCanvasLayerTextureUpdater.cpp', + 'BitmapCanvasLayerTextureUpdater.h', + 'BitmapSkPictureCanvasLayerTextureUpdater.cpp', + 'BitmapSkPictureCanvasLayerTextureUpdater.h', + 'CCActiveAnimation.cpp', + 'CCActiveAnimation.h', + 'CCActiveGestureAnimation.cpp', + 'CCActiveGestureAnimation.h', + 'CCAnimationCurve.cpp', + 'CCAnimationCurve.h', + 'CCAnimationEvents.h', + 'CCCheckerboardDrawQuad.cpp', + 'CCCheckerboardDrawQuad.h', + 'CCCompletionEvent.h', + 'CCDamageTracker.cpp', + 'CCDamageTracker.h', + 'CCDebugBorderDrawQuad.cpp', + 'CCDebugBorderDrawQuad.h', + 'CCDebugRectHistory.cpp', + 'CCDebugRectHistory.h', + 'CCDelayBasedTimeSource.cpp', + 'CCDelayBasedTimeSource.h', + 'CCDirectRenderer.cpp', + 'CCDirectRenderer.h', + 'CCDrawQuad.cpp', + 'CCDrawQuad.h', + 'CCFontAtlas.cpp', + 'CCFontAtlas.h', + 'CCFrameRateController.cpp', + 'CCFrameRateController.h', + 'CCFrameRateCounter.cpp', + 'CCFrameRateCounter.h', + 'CCGestureCurve.h', + 'CCGraphicsContext.h', + 'CCHeadsUpDisplayLayerImpl.cpp', + 'CCHeadsUpDisplayLayerImpl.h', + 'CCIOSurfaceDrawQuad.cpp', + 'CCIOSurfaceDrawQuad.h', + 'CCIOSurfaceLayerImpl.cpp', + 'CCIOSurfaceLayerImpl.h', + 'CCInputHandler.h', + 'CCKeyframedAnimationCurve.cpp', + 'CCKeyframedAnimationCurve.h', + 'CCLayerAnimationController.cpp', + 'CCLayerAnimationController.h', + 'CCLayerImpl.cpp', + 'CCLayerImpl.h', + 'CCLayerIterator.cpp', + 'CCLayerIterator.h', + 'CCLayerQuad.cpp', + 'CCLayerQuad.h', + 'CCLayerSorter.cpp', + 'CCLayerSorter.h', + 'CCLayerTilingData.cpp', + 'CCLayerTilingData.h', + 'CCLayerTreeHost.cpp', + 'CCLayerTreeHost.h', + 'CCLayerTreeHostCommon.cpp', + 'CCLayerTreeHostCommon.h', + 'CCLayerTreeHostImpl.cpp', + 'CCLayerTreeHostImpl.h', + 'CCMathUtil.cpp', + 'CCMathUtil.h', + 'CCOcclusionTracker.cpp', + 'CCOcclusionTracker.h', + 'CCOverdrawMetrics.cpp', + 'CCOverdrawMetrics.h', + 'CCPageScaleAnimation.cpp', + 'CCPageScaleAnimation.h', + 'CCPrioritizedTexture.cpp', + 'CCPrioritizedTexture.h', + 'CCPrioritizedTextureManager.cpp', + 'CCPrioritizedTextureManager.h', + 'CCPriorityCalculator.cpp', + 'CCPriorityCalculator.h', + 'CCProxy.cpp', + 'CCProxy.h', + 'CCQuadCuller.cpp', + 'CCQuadCuller.h', + 'CCQuadSink.h', + 'CCRenderPass.cpp', + 'CCRenderPass.h', + 'CCRenderPassDrawQuad.cpp', + 'CCRenderPassDrawQuad.h', + 'CCRenderSurface.cpp', + 'CCRenderSurface.h', + 'CCRenderSurfaceFilters.cpp', + 'CCRenderSurfaceFilters.h', + 'CCRenderer.h', + 'CCRendererGL.cpp', + 'CCRendererGL.h', + 'CCRenderingStats.h', + 'CCResourceProvider.cpp', + 'CCResourceProvider.h', + 'CCScheduler.cpp', + 'CCScheduler.h', + 'CCSchedulerStateMachine.cpp', + 'CCSchedulerStateMachine.h', + 'CCScopedTexture.cpp', + 'CCScopedTexture.h', + 'CCScopedThreadProxy.h', + 'CCScrollbarAnimationController.cpp', + 'CCScrollbarAnimationController.h', + 'CCScrollbarAnimationControllerLinearFade.cpp', + 'CCScrollbarAnimationControllerLinearFade.h', + 'CCScrollbarLayerImpl.cpp', + 'CCScrollbarLayerImpl.h', + 'CCSettings.cpp', + 'CCSettings.h', + 'CCSharedQuadState.cpp', + 'CCSharedQuadState.h', + 'CCSingleThreadProxy.cpp', + 'CCSingleThreadProxy.h', + 'CCSolidColorDrawQuad.cpp', + 'CCSolidColorDrawQuad.h', + 'CCSolidColorLayerImpl.cpp', + 'CCSolidColorLayerImpl.h', + 'CCStreamVideoDrawQuad.cpp', + 'CCStreamVideoDrawQuad.h', + 'CCTexture.cpp', + 'CCTexture.h', + 'CCTextureDrawQuad.cpp', + 'CCTextureDrawQuad.h', + 'CCTextureLayerImpl.cpp', + 'CCTextureLayerImpl.h', + 'CCTextureUpdateController.cpp', + 'CCTextureUpdateController.h', + 'CCTextureUpdateQueue.cpp', + 'CCTextureUpdateQueue.h', + 'CCThread.h', + 'CCThreadProxy.cpp', + 'CCThreadProxy.h', + 'CCThreadTask.h', + 'CCTileDrawQuad.cpp', + 'CCTileDrawQuad.h', + 'CCTiledLayerImpl.cpp', + 'CCTiledLayerImpl.h', + 'CCTimeSource.h', + 'CCTimer.cpp', + 'CCTimer.h', + 'CCTimingFunction.cpp', + 'CCTimingFunction.h', + 'CCVideoLayerImpl.cpp', + 'CCVideoLayerImpl.h', + 'CCYUVVideoDrawQuad.cpp', + 'CCYUVVideoDrawQuad.h', + 'CanvasLayerTextureUpdater.cpp', + 'CanvasLayerTextureUpdater.h', + 'ContentLayerChromium.cpp', + 'ContentLayerChromium.h', + 'FrameBufferSkPictureCanvasLayerTextureUpdater.cpp', + 'FrameBufferSkPictureCanvasLayerTextureUpdater.h', + 'GeometryBinding.cpp', + 'GeometryBinding.h', + 'HeadsUpDisplayLayerChromium.cpp', + 'HeadsUpDisplayLayerChromium.h', + 'IOSurfaceLayerChromium.cpp', + 'IOSurfaceLayerChromium.h', + 'ImageLayerChromium.cpp', + 'ImageLayerChromium.h', + 'LayerChromium.cpp', + 'LayerChromium.h', + 'LayerPainterChromium.h', + 'LayerTextureSubImage.cpp', + 'LayerTextureSubImage.h', + 'LayerTextureUpdater.h', + 'PlatformColor.h', + 'ProgramBinding.cpp', + 'ProgramBinding.h', + 'RateLimiter.cpp', + 'RateLimiter.h', + 'RenderSurfaceChromium.cpp', + 'RenderSurfaceChromium.h', + 'ScrollbarLayerChromium.cpp', + 'ScrollbarLayerChromium.h', + 'ShaderChromium.cpp', + 'ShaderChromium.h', + 'SkPictureCanvasLayerTextureUpdater.cpp', + 'SkPictureCanvasLayerTextureUpdater.h', + 'SolidColorLayerChromium.cpp', + 'SolidColorLayerChromium.h', + 'TextureCopier.cpp', + 'TextureCopier.h', + 'TextureLayerChromium.cpp', + 'TextureLayerChromium.h', + 'TextureUploader.h', + 'ThrottledTextureUploader.cpp', + 'ThrottledTextureUploader.h', + 'UnthrottledTextureUploader.h', + 'TiledLayerChromium.cpp', + 'TiledLayerChromium.h', + 'TreeSynchronizer.cpp', + 'TreeSynchronizer.h', + 'VideoLayerChromium.cpp', + 'VideoLayerChromium.h', + ], }, - 'targets': [ { 'target_name': 'cc', 'type': 'static_library', - } - ] + 'conditions': [ + ['use_libcc_for_compositor==1', { + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '<(DEPTH)/skia/skia.gyp:skia', + '<(DEPTH)/third_party/WebKit/Source/Platform/Platform.gyp/Platform.gyp:webkit_platform', + # We have to depend on WTF directly to pick up the correct defines for WTF headers - for instance USE_SYSTEM_MALLOC. + '<(DEPTH)/third_party/WebKit/Source/WTF/WTF.gyp/WTF.gyp:wtf', + '<(DEPTH)/ui/gl/gl.gyp:gl', + '<(DEPTH)/ui/ui.gyp:ui', + ], + 'defines': [ + 'WTF_USE_ACCELERATED_COMPOSITING=1', + ], + 'include_dirs': [ + 'stubs', + ], + 'sources': [ + '<@(cc_source_files)', + 'stubs/Extensions3DChromium.h', + 'stubs/Extensions3D.h', + 'stubs/GraphicsContext3D.h', + 'stubs/GraphicsTypes3D.h', + 'stubs/NotImplemented.h', + 'stubs/Region.h', + 'stubs/ScrollbarThemeClient.h', + 'stubs/ScrollTypes.h', + 'stubs/SharedGraphicsContext3D.h', + 'stubs/SkiaUtils.h', + 'stubs/TextStream.h', + 'stubs/TilingData.h', + 'stubs/Timer.h', + 'stubs/TraceEvent.h', + 'stubs/UnitBezier.h', + ], + }], + ], + }, + ], } diff --git a/cc/cc_tests.gyp b/cc/cc_tests.gyp new file mode 100644 index 0000000..5e873f8 --- /dev/null +++ b/cc/cc_tests.gyp @@ -0,0 +1,105 @@ +# Copyright (c) 2012 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. + +{ + 'variables': { + 'chromium_code': 0, + 'use_libcc_for_compositor%': 0, + 'cc_tests_source_files': [ + 'CCActiveAnimationTest.cpp', + 'CCDamageTrackerTest.cpp', + 'CCDelayBasedTimeSourceTest.cpp', + 'CCFrameRateControllerTest.cpp', + 'CCKeyframedAnimationCurveTest.cpp', + 'CCLayerAnimationControllerTest.cpp', + 'CCLayerImplTest.cpp', + 'CCLayerIteratorTest.cpp', + 'CCLayerQuadTest.cpp', + 'CCLayerSorterTest.cpp', + 'CCLayerTreeHostCommonTest.cpp', + 'CCLayerTreeHostImplTest.cpp', + 'CCLayerTreeHostTest.cpp', + 'CCMathUtilTest.cpp', + 'CCOcclusionTrackerTest.cpp', + 'CCPrioritizedTextureTest.cpp', + 'CCQuadCullerTest.cpp', + 'CCRenderSurfaceFiltersTest.cpp', + 'CCRenderSurfaceTest.cpp', + 'CCResourceProviderTest.cpp', + 'CCSchedulerStateMachineTest.cpp', + 'CCSchedulerTest.cpp', + 'CCSchedulerTest.cpp', + 'CCScopedTextureTest.cpp', + 'CCScrollbarAnimationControllerLinearFadeTest.cpp', + 'CCSolidColorLayerImplTest.cpp', + 'CCTextureUpdateControllerTest.cpp', + 'CCThreadTaskTest.cpp', + 'CCThreadedTest.cpp', + 'CCThreadedTest.h', + 'CCTiledLayerImplTest.cpp', + 'CCTimerTest.cpp', + 'test/CCAnimationTestCommon.cpp', + 'test/CCAnimationTestCommon.h', + 'test/CCLayerTestCommon.cpp', + 'test/CCLayerTestCommon.h', + 'test/CCLayerTreeTestCommon.h', + 'test/CCLayerTreeTestCommon.h', + 'test/CCOcclusionTrackerTestCommon.h', + 'test/CCSchedulerTestCommon.h', + 'test/CCSchedulerTestCommon.h', + 'test/CCTestCommon.h', + 'test/CCTiledLayerTestCommon.cpp', + 'test/CCTiledLayerTestCommon.h', + 'test/CompositorFakeWebGraphicsContext3D.h', + 'test/FakeCCGraphicsContext.h', + 'test/FakeCCLayerTreeHostClient.h', + 'test/FakeGraphicsContext3DTest.cpp', + 'test/FakeWebCompositorOutputSurface.h', + 'test/FakeWebGraphicsContext3D.h', + 'test/FakeWebScrollbarThemeGeometry.h', + 'test/MockCCQuadCuller.h', + ] + }, + 'conditions': [ + ['use_libcc_for_compositor==1 and component!="shared_library"', { + 'targets': [ + { + 'target_name': 'cc_unittests', + 'type': 'executable', + 'dependencies': [ + '<(DEPTH)/base/base.gyp:test_support_base', + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(DEPTH)/webkit/support/webkit_support.gyp:webkit_support', + '<(DEPTH)/skia/skia.gyp:skia', + # We have to depend on WTF directly to pick up the correct defines for WTF headers - for instance USE_SYSTEM_MALLOC. + '<(DEPTH)/third_party/WebKit/Source/WTF/WTF.gyp/WTF.gyp:wtf', + '<(DEPTH)/third_party/WebKit/Source/Platform/Platform.gyp/Platform.gyp:webkit_platform', + 'cc.gyp:cc', + ], + 'defines': [ + 'WTF_USE_ACCELERATED_COMPOSITING=1', + ], + 'include_dirs': [ + 'stubs', + 'test', + '.', + ], + 'sources': [ + '<@(cc_tests_source_files)', + 'test/run_all_unittests.cc', + ], + }, + ], + }, { + 'targets': [ + { + 'target_name': 'cc_unittests', + 'type': 'none', + } + ] + }], + ], +} + diff --git a/cc/copyfiles.py b/cc/copyfiles.py new file mode 100644 index 0000000..8366ccb --- /dev/null +++ b/cc/copyfiles.py @@ -0,0 +1,78 @@ +# Copyright (c) 2012 Google Inc. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import re +import shutil + +prefixes = ["../third_party/WebKit/Source/WebCore/platform/graphics/chromium", + "../third_party/WebKit/Source/WebCore/platform/graphics/chromium/cc", + "../third_party/WebKit/Source/WebKit/chromium/tests"] + +def Copy(name): + src = name + dst = name + if name.startswith("test/"): + src = src[5:] + fullsrc = "" + for prefix in prefixes: + candidate = "%s/%s" % (prefix, src) + if os.path.exists(candidate): + fullsrc = candidate + break + assert fullsrc != "" + shutil.copyfile(fullsrc, dst) + print "copying from %s to %s" % (fullsrc, dst) + return dst + +def FixCopyrightHeaderText(text, year): + header_template = """// Copyright %s 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. +""" + + while text[0].find(" */") == -1: + text = text[1:] + text = text[1:] + + return (header_template % year) + "".join(text) + +def FixCopyrightHeader(filepath): + with open(filepath, "r") as f: + text = f.readlines() + + if filepath.endswith("CCPageScaleAnimation.cpp"): + year = 2012 + else: + pattern = ".*Copyright \(C\) (20[01][0-9])" + m = re.match(pattern, text[0]) + if m == None: + m = re.match(pattern, text[1]) + assert m + year = m.group(1) + + fixed_text = FixCopyrightHeaderText(text, year) + with open(filepath, "w") as f: + f.write(fixed_text) + +def Readfile(gypfile): + with open(gypfile, "r") as cc_gyp: + obj = eval(cc_gyp.read()) + return obj + +def Main(): + files = Readfile("cc.gyp")['variables']['cc_source_files'] + for f in files: + dst = Copy(f) + FixCopyrightHeader(dst) + + files = Readfile("cc_tests.gyp")['variables']['cc_tests_source_files'] + for f in files: + dst = Copy(f) + FixCopyrightHeader(dst) + +if __name__ == '__main__': + import sys + os.chdir(os.path.dirname(__file__)) + sys.exit(Main()) diff --git a/cc/stubs/Extensions3D.h b/cc/stubs/Extensions3D.h new file mode 100644 index 0000000..b2e34d7 --- /dev/null +++ b/cc/stubs/Extensions3D.h @@ -0,0 +1,26 @@ +// Copyright (c) 2012 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. + +#ifndef CC_STUBS_EXTENSIONS3D_H_ +#define CC_STUBS_EXTENSIONS3D_H_ + +#include "third_party/khronos/GLES2/gl2.h" +#include "third_party/khronos/GLES2/gl2ext.h" + +namespace WebCore { + +class Extensions3D { +public: + enum { + TEXTURE_RECTANGLE_ARB = GL_TEXTURE_RECTANGLE_ARB, + BGRA_EXT = GL_BGRA_EXT, + RGBA8_OES = GL_RGBA8_OES, + }; +}; + +} + + +#endif // CC_STUBS_EXTENSIONS3D_H_ + diff --git a/cc/stubs/Extensions3DChromium.h b/cc/stubs/Extensions3DChromium.h new file mode 100644 index 0000000..335023c --- /dev/null +++ b/cc/stubs/Extensions3DChromium.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012 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. + +#ifndef CC_STUBS_EXTENSIONS3DCHROMIUM_H_ +#define CC_STUBS_EXTENSIONS3DCHROMIUM_H_ + +#include "Extensions3D.h" + +// These enum names collides with gl2ext.h, so we just redefine them. +#ifdef GL_TEXTURE_EXTERNAL_OES +#undef GL_TEXTURE_EXTERNAL_OES +#endif +#ifdef GL_TEXTURE_USAGE_ANGLE +#undef GL_TEXTURE_USAGE_ANGLE +#endif +#ifdef GL_FRAMEBUFFER_ATTACHMENT_ANGLE +#undef GL_FRAMEBUFFER_ATTACHMENT_ANGLE +#endif + +namespace WebCore { + +class Extensions3DChromium { +public: + enum { + // GL_OES_EGL_image_external + GL_TEXTURE_EXTERNAL_OES = 0x8D65, + + // GL_CHROMIUM_map_sub (enums inherited from GL_ARB_vertex_buffer_object) + READ_ONLY = 0x88B8, + WRITE_ONLY = 0x88B9, + + // GL_ANGLE_texture_usage + GL_TEXTURE_USAGE_ANGLE = 0x93A2, + GL_FRAMEBUFFER_ATTACHMENT_ANGLE = 0x93A3, + + // GL_EXT_texture_storage + BGRA8_EXT = 0x93A1, + + // GL_EXT_occlusion_query_boolean + ANY_SAMPLES_PASSED_EXT = 0x8C2F, + ANY_SAMPLES_PASSED_CONSERVATIVE_EXT = 0x8D6A, + CURRENT_QUERY_EXT = 0x8865, + QUERY_RESULT_EXT = 0x8866, + QUERY_RESULT_AVAILABLE_EXT = 0x8867, + + // GL_CHROMIUM_command_buffer_query + COMMANDS_ISSUED_CHROMIUM = 0x84F2 + }; +}; + +} + +#endif // CC_STUBS_EXTENSIONS3DCHROMIUM_H_ + diff --git a/cc/stubs/GraphicsContext3D.h b/cc/stubs/GraphicsContext3D.h new file mode 100644 index 0000000..41404f2 --- /dev/null +++ b/cc/stubs/GraphicsContext3D.h @@ -0,0 +1,76 @@ +// Copyright (c) 2012 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. + +#ifndef CC_STUBS_GRAPHICSCONTEXT3D_H_ +#define CC_STUBS_GRAPHICSCONTEXT3D_H_ + +#include "GraphicsTypes3D.h" +#include "IntSize.h" +#include "third_party/khronos/GLES2/gl2.h" + +namespace WebCore { + +class GraphicsContext3D { +public: + enum SourceDataFormat { SourceFormatRGBA8, SourceFormatBGRA8 }; + static bool computeFormatAndTypeParameters(unsigned, unsigned, unsigned* componentsPerPixel, unsigned* bytesPerComponent) + { + *componentsPerPixel = 4; + *bytesPerComponent = 1; + return true; + } + + enum { + ARRAY_BUFFER = GL_ARRAY_BUFFER, + BLEND = GL_BLEND, + CLAMP_TO_EDGE = GL_CLAMP_TO_EDGE, + COLOR_ATTACHMENT0 = GL_COLOR_ATTACHMENT0, + COLOR_BUFFER_BIT = GL_COLOR_BUFFER_BIT, + COMPILE_STATUS = GL_COMPILE_STATUS, + CULL_FACE = GL_CULL_FACE, + DEPTH_TEST = GL_DEPTH_TEST, + ELEMENT_ARRAY_BUFFER = GL_ELEMENT_ARRAY_BUFFER, + EXTENSIONS = GL_EXTENSIONS, + FLOAT = GL_FLOAT, + FRAGMENT_SHADER = GL_FRAGMENT_SHADER, + FRAMEBUFFER_COMPLETE = GL_FRAMEBUFFER_COMPLETE, + FRAMEBUFFER = GL_FRAMEBUFFER, + INVALID_ENUM = GL_INVALID_ENUM, + INVALID_VALUE = GL_INVALID_VALUE, + LINEAR = GL_LINEAR, + LINE_LOOP = GL_LINE_LOOP , + LINK_STATUS = GL_LINK_STATUS, + LUMINANCE = GL_LUMINANCE, + MAX_TEXTURE_SIZE = GL_MAX_TEXTURE_SIZE, + NEAREST = GL_NEAREST, + NO_ERROR = GL_NO_ERROR, + ONE = GL_ONE, + ONE_MINUS_SRC_ALPHA = GL_ONE_MINUS_SRC_ALPHA, + RGBA = GL_RGBA, + RGB = GL_RGB, + SCISSOR_TEST = GL_SCISSOR_TEST, + SRC_ALPHA = GL_SRC_ALPHA, + STATIC_DRAW = GL_STATIC_DRAW, + TEXTURE0 = GL_TEXTURE0, + TEXTURE1 = GL_TEXTURE1, + TEXTURE_2D = GL_TEXTURE_2D, + TEXTURE2 = GL_TEXTURE2, + TEXTURE3 = GL_TEXTURE3, + TEXTURE_MAG_FILTER = GL_TEXTURE_MAG_FILTER, + TEXTURE_MIN_FILTER = GL_TEXTURE_MIN_FILTER, + TEXTURE_WRAP_S = GL_TEXTURE_WRAP_S, + TEXTURE_WRAP_T = GL_TEXTURE_WRAP_T, + TRIANGLES = GL_TRIANGLES, + TRIANGLE_FAN = GL_TRIANGLE_FAN, + UNSIGNED_BYTE = GL_UNSIGNED_BYTE, + UNSIGNED_SHORT = GL_UNSIGNED_SHORT, + VERTEX_SHADER = GL_VERTEX_SHADER, + ZERO = GL_ZERO, + }; +}; + +} + +#endif // CC_STUBS_GRAPHICSCONTEXT3D_H_ + diff --git a/cc/stubs/GraphicsTypes3D.h b/cc/stubs/GraphicsTypes3D.h new file mode 100644 index 0000000..a01414e --- /dev/null +++ b/cc/stubs/GraphicsTypes3D.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012 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. + +#ifndef CC_STUBS_GRAPHICSTYPES3D_H_ +#define CC_STUBS_GRAPHICSTYPES3D_H_ + +#define NullPlatform3DObject 0 + +typedef unsigned char GC3Dboolean; +typedef unsigned GC3Denum; +typedef unsigned GC3Duint; +typedef unsigned Platform3DObject; +typedef int GC3Dint; +typedef signed char GC3Dbyte; + +#endif // CC_STUBS_GRAPHICSTYPES3D_H_ + diff --git a/cc/stubs/NotImplemented.h b/cc/stubs/NotImplemented.h new file mode 100644 index 0000000..7c6bba5 --- /dev/null +++ b/cc/stubs/NotImplemented.h @@ -0,0 +1,10 @@ +// Copyright (c) 2012 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. + +#ifndef CC_STUBS_NOTIMPLEMENTED_H +#define CC_STUBS_NOTIMPLEMENTED_H + +#define notImplemented() do { } while(0) + +#endif // CC_STUBS_NOTIMPLEMENTED_H diff --git a/cc/stubs/Region.h b/cc/stubs/Region.h new file mode 100644 index 0000000..c7f8f50 --- /dev/null +++ b/cc/stubs/Region.h @@ -0,0 +1,10 @@ +// Copyright (c) 2012 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. + +#ifndef CC_STUBS_REGION_H_ +#define CC_STUBS_REGION_H_ + +#include "third_party/WebKit/Source/WebCore/platform/graphics/Region.h" + +#endif // CC_STUBS_REGION_H_ diff --git a/cc/stubs/SkiaUtils.h b/cc/stubs/SkiaUtils.h new file mode 100644 index 0000000..f5ceb4eb --- /dev/null +++ b/cc/stubs/SkiaUtils.h @@ -0,0 +1,17 @@ +// Copyright (c) 2012 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. + +#ifndef CC_STUBS_SKIAUTILS_H_ +#define CC_STUBS_SKIAUTILS_H_ + +namespace WebCore { + +inline SkScalar WebCoreFloatToSkScalar(float f) +{ + return SkFloatToScalar(isfinite(f) ? f : 0); +} + +} + +#endif // CC_STUBS_SKIAUTILS_H_ diff --git a/cc/stubs/TextStream.h b/cc/stubs/TextStream.h new file mode 100644 index 0000000..a9d02d6 --- /dev/null +++ b/cc/stubs/TextStream.h @@ -0,0 +1,23 @@ +// Copyright (c) 2012 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. + +#ifndef CC_STUBS_TEXTSTREAM_H_ +#define CC_STUBS_TEXTSTREAM_H_ + +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class TextStream { +public: + TextStream& operator<<(const String&) { return *this; } + TextStream& operator<<(const char*) { return *this; } + TextStream& operator<<(int) { return *this; } + String release() { return ""; } +}; + +} + +#endif // CC_STUBS_TEXTSTREAM_H_ + diff --git a/cc/stubs/TilingData.h b/cc/stubs/TilingData.h new file mode 100644 index 0000000..03e48eb --- /dev/null +++ b/cc/stubs/TilingData.h @@ -0,0 +1,10 @@ +// Copyright (c) 2012 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. + +#ifndef CC_STUBS_TILINGDATA_H_ +#define CC_STUBS_TILINGDATA_H_ + +#include "third_party/WebKit/Source/WebCore/platform/graphics/gpu/TilingData.h" + +#endif // CC_STUBS_TILINGDATA_H_ diff --git a/cc/stubs/TraceEvent.h b/cc/stubs/TraceEvent.h new file mode 100644 index 0000000..75587a3 --- /dev/null +++ b/cc/stubs/TraceEvent.h @@ -0,0 +1,10 @@ +// Copyright (c) 2012 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. + +// Chromium's LOG() macro collides with one from WTF. +#ifdef LOG +#undef LOG +#endif + +#include "base/debug/trace_event.h" diff --git a/cc/stubs/UnitBezier.h b/cc/stubs/UnitBezier.h new file mode 100644 index 0000000..90f810c --- /dev/null +++ b/cc/stubs/UnitBezier.h @@ -0,0 +1,6 @@ +// Copyright (c) 2012 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. + +// TODO(jamesr): Remove or refactor this dependency. +#include "third_party/WebKit/Source/WebCore/platform/graphics/UnitBezier.h" diff --git a/cc/test/CCAnimationTestCommon.cpp b/cc/test/CCAnimationTestCommon.cpp new file mode 100644 index 0000000..db09228 --- /dev/null +++ b/cc/test/CCAnimationTestCommon.cpp @@ -0,0 +1,156 @@ +// Copyright 2012 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 "CCAnimationTestCommon.h" + +#include "CCKeyframedAnimationCurve.h" +#include "CCLayerAnimationController.h" +#include "CCLayerImpl.h" +#include "LayerChromium.h" +#include <public/WebTransformOperations.h> + +using namespace WebCore; + +namespace { + +template <class Target> +void addOpacityTransition(Target& target, double duration, float startOpacity, float endOpacity, bool useTimingFunction) +{ + OwnPtr<CCKeyframedFloatAnimationCurve> curve(CCKeyframedFloatAnimationCurve::create()); + + if (duration > 0) + curve->addKeyframe(CCFloatKeyframe::create(0, startOpacity, useTimingFunction ? nullptr : CCEaseTimingFunction::create())); + curve->addKeyframe(CCFloatKeyframe::create(duration, endOpacity, nullptr)); + + OwnPtr<CCActiveAnimation> animation(CCActiveAnimation::create(curve.release(), 0, 0, CCActiveAnimation::Opacity)); + animation->setNeedsSynchronizedStartTime(true); + + target.addAnimation(animation.release()); +} + +template <class Target> +void addAnimatedTransform(Target& target, double duration, int deltaX, int deltaY) +{ + static int id = 0; + OwnPtr<CCKeyframedTransformAnimationCurve> curve(CCKeyframedTransformAnimationCurve::create()); + + if (duration > 0) { + WebKit::WebTransformOperations startOperations; + startOperations.appendTranslate(deltaX, deltaY, 0); + curve->addKeyframe(CCTransformKeyframe::create(0, startOperations, nullptr)); + } + + WebKit::WebTransformOperations operations; + operations.appendTranslate(deltaX, deltaY, 0); + curve->addKeyframe(CCTransformKeyframe::create(duration, operations, nullptr)); + + OwnPtr<CCActiveAnimation> animation(CCActiveAnimation::create(curve.release(), id++, 0, CCActiveAnimation::Transform)); + animation->setNeedsSynchronizedStartTime(true); + + target.addAnimation(animation.release()); +} + +} // namespace + +namespace WebKitTests { + +FakeFloatAnimationCurve::FakeFloatAnimationCurve() +{ +} + +FakeFloatAnimationCurve::~FakeFloatAnimationCurve() +{ +} + +PassOwnPtr<WebCore::CCAnimationCurve> FakeFloatAnimationCurve::clone() const +{ + return adoptPtr(new FakeFloatAnimationCurve); +} + +FakeTransformTransition::FakeTransformTransition(double duration) + : m_duration(duration) +{ +} + +FakeTransformTransition::~FakeTransformTransition() +{ +} + +WebKit::WebTransformationMatrix FakeTransformTransition::getValue(double time) const +{ + return WebKit::WebTransformationMatrix(); +} + +PassOwnPtr<WebCore::CCAnimationCurve> FakeTransformTransition::clone() const +{ + return adoptPtr(new FakeTransformTransition(*this)); +} + + +FakeFloatTransition::FakeFloatTransition(double duration, float from, float to) + : m_duration(duration) + , m_from(from) + , m_to(to) +{ +} + +FakeFloatTransition::~FakeFloatTransition() +{ +} + +float FakeFloatTransition::getValue(double time) const +{ + time /= m_duration; + if (time >= 1) + time = 1; + return (1 - time) * m_from + time * m_to; +} + +FakeLayerAnimationControllerClient::FakeLayerAnimationControllerClient() + : m_opacity(0) +{ +} + +FakeLayerAnimationControllerClient::~FakeLayerAnimationControllerClient() +{ +} + +PassOwnPtr<WebCore::CCAnimationCurve> FakeFloatTransition::clone() const +{ + return adoptPtr(new FakeFloatTransition(*this)); +} + +void addOpacityTransitionToController(WebCore::CCLayerAnimationController& controller, double duration, float startOpacity, float endOpacity, bool useTimingFunction) +{ + addOpacityTransition(controller, duration, startOpacity, endOpacity, useTimingFunction); +} + +void addAnimatedTransformToController(WebCore::CCLayerAnimationController& controller, double duration, int deltaX, int deltaY) +{ + addAnimatedTransform(controller, duration, deltaX, deltaY); +} + +void addOpacityTransitionToLayer(WebCore::LayerChromium& layer, double duration, float startOpacity, float endOpacity, bool useTimingFunction) +{ + addOpacityTransition(layer, duration, startOpacity, endOpacity, useTimingFunction); +} + +void addOpacityTransitionToLayer(WebCore::CCLayerImpl& layer, double duration, float startOpacity, float endOpacity, bool useTimingFunction) +{ + addOpacityTransition(*layer.layerAnimationController(), duration, startOpacity, endOpacity, useTimingFunction); +} + +void addAnimatedTransformToLayer(WebCore::LayerChromium& layer, double duration, int deltaX, int deltaY) +{ + addAnimatedTransform(layer, duration, deltaX, deltaY); +} + +void addAnimatedTransformToLayer(WebCore::CCLayerImpl& layer, double duration, int deltaX, int deltaY) +{ + addAnimatedTransform(*layer.layerAnimationController(), duration, deltaX, deltaY); +} + +} // namespace WebKitTests diff --git a/cc/test/CCAnimationTestCommon.h b/cc/test/CCAnimationTestCommon.h new file mode 100644 index 0000000..9f13643 --- /dev/null +++ b/cc/test/CCAnimationTestCommon.h @@ -0,0 +1,90 @@ +// Copyright 2012 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. + +#ifndef CCAnimationTestCommon_h +#define CCAnimationTestCommon_h + +#include "CCActiveAnimation.h" +#include "CCAnimationCurve.h" +#include "CCLayerAnimationController.h" +#include "IntSize.h" + +#include <wtf/OwnPtr.h> + +namespace WebCore { +class CCLayerImpl; +class LayerChromium; +} + +namespace WebKitTests { + +class FakeFloatAnimationCurve : public WebCore::CCFloatAnimationCurve { +public: + FakeFloatAnimationCurve(); + virtual ~FakeFloatAnimationCurve(); + + virtual double duration() const OVERRIDE { return 1; } + virtual float getValue(double now) const OVERRIDE { return 0; } + virtual PassOwnPtr<WebCore::CCAnimationCurve> clone() const OVERRIDE; +}; + +class FakeTransformTransition : public WebCore::CCTransformAnimationCurve { +public: + FakeTransformTransition(double duration); + virtual ~FakeTransformTransition(); + + virtual double duration() const OVERRIDE { return m_duration; } + virtual WebKit::WebTransformationMatrix getValue(double time) const OVERRIDE; + + virtual PassOwnPtr<WebCore::CCAnimationCurve> clone() const OVERRIDE; + +private: + double m_duration; +}; + +class FakeFloatTransition : public WebCore::CCFloatAnimationCurve { +public: + FakeFloatTransition(double duration, float from, float to); + virtual ~FakeFloatTransition(); + + virtual double duration() const OVERRIDE { return m_duration; } + virtual float getValue(double time) const OVERRIDE; + + virtual PassOwnPtr<WebCore::CCAnimationCurve> clone() const OVERRIDE; + +private: + double m_duration; + float m_from; + float m_to; +}; + +class FakeLayerAnimationControllerClient : public WebCore::CCLayerAnimationControllerClient { +public: + FakeLayerAnimationControllerClient(); + virtual ~FakeLayerAnimationControllerClient(); + + // CCLayerAnimationControllerClient implementation + virtual int id() const OVERRIDE { return 0; } + virtual void setOpacityFromAnimation(float opacity) OVERRIDE { m_opacity = opacity; } + virtual float opacity() const OVERRIDE { return m_opacity; } + virtual void setTransformFromAnimation(const WebKit::WebTransformationMatrix& transform) OVERRIDE { m_transform = transform; } + virtual const WebKit::WebTransformationMatrix& transform() const OVERRIDE { return m_transform; } + +private: + float m_opacity; + WebKit::WebTransformationMatrix m_transform; +}; + +void addOpacityTransitionToController(WebCore::CCLayerAnimationController&, double duration, float startOpacity, float endOpacity, bool useTimingFunction); +void addAnimatedTransformToController(WebCore::CCLayerAnimationController&, double duration, int deltaX, int deltaY); + +void addOpacityTransitionToLayer(WebCore::LayerChromium&, double duration, float startOpacity, float endOpacity, bool useTimingFunction); +void addOpacityTransitionToLayer(WebCore::CCLayerImpl&, double duration, float startOpacity, float endOpacity, bool useTimingFunction); + +void addAnimatedTransformToLayer(WebCore::LayerChromium&, double duration, int deltaX, int deltaY); +void addAnimatedTransformToLayer(WebCore::CCLayerImpl&, double duration, int deltaX, int deltaY); + +} // namespace WebKitTests + +#endif // CCAnimationTesctCommon_h diff --git a/cc/test/CCLayerTestCommon.cpp b/cc/test/CCLayerTestCommon.cpp new file mode 100644 index 0000000..1195012 --- /dev/null +++ b/cc/test/CCLayerTestCommon.cpp @@ -0,0 +1,36 @@ +// Copyright 2012 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 "CCLayerTestCommon.h" +#include "CCDrawQuad.h" + +#include <gtest/gtest.h> +#include <wtf/Vector.h> + +using namespace WebCore; + +namespace CCLayerTestCommon { + +// Align with expected and actual output +const char* quadString = " Quad: "; + +void verifyQuadsExactlyCoverRect(const CCQuadList& quads, const IntRect& rect) +{ + Region remaining(rect); + + for (size_t i = 0; i < quads.size(); ++i) { + CCDrawQuad* quad = quads[i].get(); + IntRect quadRect = quad->quadRect(); + + EXPECT_TRUE(rect.contains(quadRect)) << quadString << i; + EXPECT_TRUE(remaining.contains(quadRect)) << quadString << i; + remaining.subtract(Region(quadRect)); + } + + EXPECT_TRUE(remaining.isEmpty()); +} + +} // namespace diff --git a/cc/test/CCLayerTestCommon.h b/cc/test/CCLayerTestCommon.h new file mode 100644 index 0000000..fc726fa --- /dev/null +++ b/cc/test/CCLayerTestCommon.h @@ -0,0 +1,19 @@ +// Copyright 2012 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. + +#ifndef CCLayerTestCommon_h +#define CCLayerTestCommon_h + +#include "CCRenderPass.h" +#include "IntRect.h" +#include "Region.h" + +namespace CCLayerTestCommon { + +extern const char* quadString; + +void verifyQuadsExactlyCoverRect(const WebCore::CCQuadList&, const WebCore::IntRect&); + +} // namespace CCLayerTestCommon +#endif // CCLayerTestCommon_h diff --git a/cc/test/CCLayerTreeTestCommon.h b/cc/test/CCLayerTreeTestCommon.h new file mode 100644 index 0000000..1e9a473 --- /dev/null +++ b/cc/test/CCLayerTreeTestCommon.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef CCLayerTreeTestCommon_h +#define CCLayerTreeTestCommon_h + +#include <public/WebTransformationMatrix.h> + +namespace WebKitTests { + +// These are macros instead of functions so that we get useful line numbers where a test failed. +#define EXPECT_FLOAT_RECT_EQ(expected, actual) \ + EXPECT_FLOAT_EQ((expected).location().x(), (actual).location().x()); \ + EXPECT_FLOAT_EQ((expected).location().y(), (actual).location().y()); \ + EXPECT_FLOAT_EQ((expected).size().width(), (actual).size().width()); \ + EXPECT_FLOAT_EQ((expected).size().height(), (actual).size().height()) + +#define EXPECT_INT_RECT_EQ(expected, actual) \ + EXPECT_EQ((expected).location().x(), (actual).location().x()); \ + EXPECT_EQ((expected).location().y(), (actual).location().y()); \ + EXPECT_EQ((expected).size().width(), (actual).size().width()); \ + EXPECT_EQ((expected).size().height(), (actual).size().height()) + +// This is a function rather than a macro because when this is included as a macro +// in bulk, it causes a significant slow-down in compilation time. This problem +// exists with both gcc and clang, and bugs have been filed at +// http://llvm.org/bugs/show_bug.cgi?id=13651 and http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54337 +void ExpectTransformationMatrixEq(WebKit::WebTransformationMatrix expected, + WebKit::WebTransformationMatrix actual); + +#define EXPECT_TRANSFORMATION_MATRIX_EQ(expected, actual) \ + { \ + SCOPED_TRACE(""); \ + WebKitTests::ExpectTransformationMatrixEq(expected, actual); \ + } + +} // namespace + +#endif // CCLayerTreeTestCommon_h diff --git a/cc/test/CCOcclusionTrackerTestCommon.h b/cc/test/CCOcclusionTrackerTestCommon.h new file mode 100644 index 0000000..90e0cc3 --- /dev/null +++ b/cc/test/CCOcclusionTrackerTestCommon.h @@ -0,0 +1,37 @@ +// Copyright 2012 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. + +#ifndef CCOcclusionTrackerTestCommon_h +#define CCOcclusionTrackerTestCommon_h + +#include "CCOcclusionTracker.h" +#include "CCRenderSurface.h" +#include "IntRect.h" +#include "Region.h" +#include "RenderSurfaceChromium.h" + +namespace WebKitTests { + +// A subclass to expose the total current occlusion. +template<typename LayerType, typename RenderSurfaceType> +class TestCCOcclusionTrackerBase : public WebCore::CCOcclusionTrackerBase<LayerType, RenderSurfaceType> { +public: + TestCCOcclusionTrackerBase(WebCore::IntRect screenScissorRect, bool recordMetricsForFrame = false) + : WebCore::CCOcclusionTrackerBase<LayerType, RenderSurfaceType>(screenScissorRect, recordMetricsForFrame) + { + } + + WebCore::Region occlusionInScreenSpace() const { return WebCore::CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::m_stack.last().occlusionInScreen; } + WebCore::Region occlusionInTargetSurface() const { return WebCore::CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::m_stack.last().occlusionInTarget; } + + void setOcclusionInScreenSpace(const WebCore::Region& region) { WebCore::CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::m_stack.last().occlusionInScreen = region; } + void setOcclusionInTargetSurface(const WebCore::Region& region) { WebCore::CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::m_stack.last().occlusionInTarget = region; } +}; + +typedef TestCCOcclusionTrackerBase<WebCore::LayerChromium, WebCore::RenderSurfaceChromium> TestCCOcclusionTracker; +typedef TestCCOcclusionTrackerBase<WebCore::CCLayerImpl, WebCore::CCRenderSurface> TestCCOcclusionTrackerImpl; + +} + +#endif // CCOcclusionTrackerTestCommon_h diff --git a/cc/test/CCSchedulerTestCommon.h b/cc/test/CCSchedulerTestCommon.h new file mode 100644 index 0000000..cb78510 --- /dev/null +++ b/cc/test/CCSchedulerTestCommon.h @@ -0,0 +1,133 @@ +// 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. + +#ifndef CCSchedulerTestCommon_h +#define CCSchedulerTestCommon_h + +#include "CCDelayBasedTimeSource.h" +#include "CCFrameRateController.h" +#include "CCThread.h" +#include <gtest/gtest.h> +#include <wtf/OwnPtr.h> + +namespace WebKitTests { + +class FakeCCTimeSourceClient : public WebCore::CCTimeSourceClient { +public: + FakeCCTimeSourceClient() { reset(); } + void reset() { m_tickCalled = false; } + bool tickCalled() const { return m_tickCalled; } + + virtual void onTimerTick() OVERRIDE { m_tickCalled = true; } + +protected: + bool m_tickCalled; +}; + +class FakeCCThread : public WebCore::CCThread { +public: + FakeCCThread() { reset(); } + void reset() + { + m_pendingTaskDelay = 0; + m_pendingTask.clear(); + m_runPendingTaskOnOverwrite = false; + } + + void runPendingTaskOnOverwrite(bool enable) + { + m_runPendingTaskOnOverwrite = enable; + } + + bool hasPendingTask() const { return m_pendingTask; } + void runPendingTask() + { + ASSERT(m_pendingTask); + OwnPtr<Task> task = m_pendingTask.release(); + task->performTask(); + } + + long long pendingDelayMs() const + { + EXPECT_TRUE(hasPendingTask()); + return m_pendingTaskDelay; + } + + virtual void postTask(PassOwnPtr<Task>) { ASSERT_NOT_REACHED(); } + virtual void postDelayedTask(PassOwnPtr<Task> task, long long delay) + { + if (m_runPendingTaskOnOverwrite && hasPendingTask()) + runPendingTask(); + + EXPECT_TRUE(!hasPendingTask()); + m_pendingTask = task; + m_pendingTaskDelay = delay; + } + virtual WTF::ThreadIdentifier threadID() const { return 0; } + +protected: + OwnPtr<Task> m_pendingTask; + long long m_pendingTaskDelay; + bool m_runPendingTaskOnOverwrite; +}; + +class FakeCCTimeSource : public WebCore::CCTimeSource { +public: + FakeCCTimeSource() + : m_active(false) + , m_nextTickTime(0) + , m_client(0) { } + + virtual ~FakeCCTimeSource() { } + + virtual void setClient(WebCore::CCTimeSourceClient* client) OVERRIDE { m_client = client; } + virtual void setActive(bool b) OVERRIDE { m_active = b; } + virtual bool active() const OVERRIDE { return m_active; } + virtual void setTimebaseAndInterval(double timebase, double interval) OVERRIDE { } + virtual double lastTickTime() OVERRIDE { return 0; } + virtual double nextTickTimeIfActivated() OVERRIDE { return 0; } + + void tick() + { + ASSERT(m_active); + if (m_client) + m_client->onTimerTick(); + } + + void setNextTickTime(double nextTickTime) { m_nextTickTime = nextTickTime; } + +protected: + bool m_active; + double m_nextTickTime; + WebCore::CCTimeSourceClient* m_client; +}; + +class FakeCCDelayBasedTimeSource : public WebCore::CCDelayBasedTimeSource { +public: + static PassRefPtr<FakeCCDelayBasedTimeSource> create(double interval, WebCore::CCThread* thread) + { + return adoptRef(new FakeCCDelayBasedTimeSource(interval, thread)); + } + + void setMonotonicTimeNow(double time) { m_monotonicTimeNow = time; } + virtual double monotonicTimeNow() const OVERRIDE { return m_monotonicTimeNow; } + +protected: + FakeCCDelayBasedTimeSource(double interval, WebCore::CCThread* thread) + : CCDelayBasedTimeSource(interval, thread) + , m_monotonicTimeNow(0) { } + + double m_monotonicTimeNow; +}; + +class FakeCCFrameRateController : public WebCore::CCFrameRateController { +public: + FakeCCFrameRateController(PassRefPtr<WebCore::CCTimeSource> timer) : WebCore::CCFrameRateController(timer) { } + + int numFramesPending() const { return m_numFramesPending; } +}; + +} + +#endif // CCSchedulerTestCommon_h diff --git a/cc/test/CCTestCommon.h b/cc/test/CCTestCommon.h new file mode 100644 index 0000000..c1ab34a --- /dev/null +++ b/cc/test/CCTestCommon.h @@ -0,0 +1,22 @@ +// Copyright 2012 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. + +#ifndef CCTestCommon_h +#define CCTestCommon_h + +#include "CCSettings.h" + +namespace WebKitTests { + +// If you have a test that modifies or uses global settings, keep an instance +// of this class to ensure that you start and end with a clean slate. +class CCScopedSettings { +public: + CCScopedSettings() { WebCore::CCSettings::reset(); } + ~CCScopedSettings() { WebCore::CCSettings::reset(); } +}; + +} // namespace WebKitTests + +#endif // CCTestCommon_h diff --git a/cc/test/CCTiledLayerTestCommon.cpp b/cc/test/CCTiledLayerTestCommon.cpp new file mode 100644 index 0000000..a1ba83c --- /dev/null +++ b/cc/test/CCTiledLayerTestCommon.cpp @@ -0,0 +1,120 @@ +// Copyright 2012 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 "CCTiledLayerTestCommon.h" + +using namespace WebCore; + +namespace WebKitTests { + +FakeLayerTextureUpdater::Texture::Texture(FakeLayerTextureUpdater* layer, PassOwnPtr<CCPrioritizedTexture> texture) + : LayerTextureUpdater::Texture(texture) + , m_layer(layer) +{ +} + +FakeLayerTextureUpdater::Texture::~Texture() +{ +} + +void FakeLayerTextureUpdater::Texture::updateRect(CCResourceProvider* resourceProvider, const IntRect&, const IntSize&) +{ + texture()->acquireBackingTexture(resourceProvider); + m_layer->updateRect(); +} + +void FakeLayerTextureUpdater::Texture::prepareRect(const IntRect&, WebCore::CCRenderingStats&) +{ + m_layer->prepareRect(); +} + +FakeLayerTextureUpdater::FakeLayerTextureUpdater() + : m_prepareCount(0) + , m_updateCount(0) + , m_prepareRectCount(0) +{ +} + +FakeLayerTextureUpdater::~FakeLayerTextureUpdater() +{ +} + +void FakeLayerTextureUpdater::prepareToUpdate(const IntRect& contentRect, const IntSize&, float, float, IntRect& resultingOpaqueRect, CCRenderingStats&) +{ + m_prepareCount++; + m_lastUpdateRect = contentRect; + if (!m_rectToInvalidate.isEmpty()) { + m_layer->invalidateContentRect(m_rectToInvalidate); + m_rectToInvalidate = IntRect(); + m_layer = 0; + } + resultingOpaqueRect = m_opaquePaintRect; +} + +void FakeLayerTextureUpdater::setRectToInvalidate(const IntRect& rect, FakeTiledLayerChromium* layer) +{ + m_rectToInvalidate = rect; + m_layer = layer; +} + +PassOwnPtr<LayerTextureUpdater::Texture> FakeLayerTextureUpdater::createTexture(CCPrioritizedTextureManager* manager) +{ + return adoptPtr(new Texture(this, CCPrioritizedTexture::create(manager))); +} + +FakeCCTiledLayerImpl::FakeCCTiledLayerImpl(int id) + : CCTiledLayerImpl(id) +{ +} + +FakeCCTiledLayerImpl::~FakeCCTiledLayerImpl() +{ +} + +FakeTiledLayerChromium::FakeTiledLayerChromium(CCPrioritizedTextureManager* textureManager) + : TiledLayerChromium() + , m_fakeTextureUpdater(adoptRef(new FakeLayerTextureUpdater)) + , m_textureManager(textureManager) +{ + setTileSize(tileSize()); + setTextureFormat(GraphicsContext3D::RGBA); + setBorderTexelOption(CCLayerTilingData::NoBorderTexels); + setIsDrawable(true); // So that we don't get false positives if any of these tests expect to return false from drawsContent() for other reasons. +} + +FakeTiledLayerChromium::~FakeTiledLayerChromium() +{ +} + +void FakeTiledLayerChromium::setNeedsDisplayRect(const FloatRect& rect) +{ + m_lastNeedsDisplayRect = rect; + TiledLayerChromium::setNeedsDisplayRect(rect); +} + +void FakeTiledLayerChromium::setTexturePriorities(const CCPriorityCalculator& calculator) +{ + // Ensure there is always a target render surface available. If none has been + // set (the layer is an orphan for the test), then just set a surface on itself. + bool missingTargetRenderSurface = !renderTarget(); + + if (missingTargetRenderSurface) + createRenderSurface(); + + TiledLayerChromium::setTexturePriorities(calculator); + + if (missingTargetRenderSurface) { + clearRenderSurface(); + setRenderTarget(0); + } +} + +FakeTiledLayerWithScaledBounds::FakeTiledLayerWithScaledBounds(CCPrioritizedTextureManager* textureManager) + : FakeTiledLayerChromium(textureManager) +{ +} + +} // namespace diff --git a/cc/test/CCTiledLayerTestCommon.h b/cc/test/CCTiledLayerTestCommon.h new file mode 100644 index 0000000..105a242 --- /dev/null +++ b/cc/test/CCTiledLayerTestCommon.h @@ -0,0 +1,145 @@ +// Copyright 2012 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. + +#ifndef CCTiledLayerTestCommon_h +#define CCTiledLayerTestCommon_h + +#include "CCGraphicsContext.h" +#include "CCPrioritizedTexture.h" +#include "CCResourceProvider.h" +#include "CCTextureUpdateQueue.h" +#include "CCTiledLayerImpl.h" +#include "IntRect.h" +#include "IntSize.h" +#include "LayerTextureUpdater.h" +#include "Region.h" +#include "TextureCopier.h" +#include "TextureUploader.h" +#include "TiledLayerChromium.h" + +namespace WebKitTests { + +class FakeTiledLayerChromium; + +class FakeLayerTextureUpdater : public WebCore::LayerTextureUpdater { +public: + class Texture : public WebCore::LayerTextureUpdater::Texture { + public: + Texture(FakeLayerTextureUpdater*, PassOwnPtr<WebCore::CCPrioritizedTexture>); + virtual ~Texture(); + + virtual void updateRect(WebCore::CCResourceProvider* , const WebCore::IntRect&, const WebCore::IntSize&) OVERRIDE; + virtual void prepareRect(const WebCore::IntRect&, WebCore::CCRenderingStats&) OVERRIDE; + + private: + FakeLayerTextureUpdater* m_layer; + }; + + FakeLayerTextureUpdater(); + virtual ~FakeLayerTextureUpdater(); + + virtual PassOwnPtr<WebCore::LayerTextureUpdater::Texture> createTexture(WebCore::CCPrioritizedTextureManager*) OVERRIDE; + virtual SampledTexelFormat sampledTexelFormat(GC3Denum) OVERRIDE { return SampledTexelFormatRGBA; } + + virtual void prepareToUpdate(const WebCore::IntRect& contentRect, const WebCore::IntSize&, float, float, WebCore::IntRect& resultingOpaqueRect, WebCore::CCRenderingStats&) OVERRIDE; + // Sets the rect to invalidate during the next call to prepareToUpdate(). After the next + // call to prepareToUpdate() the rect is reset. + void setRectToInvalidate(const WebCore::IntRect&, FakeTiledLayerChromium*); + // Last rect passed to prepareToUpdate(). + const WebCore::IntRect& lastUpdateRect() const { return m_lastUpdateRect; } + + // Number of times prepareToUpdate has been invoked. + int prepareCount() const { return m_prepareCount; } + void clearPrepareCount() { m_prepareCount = 0; } + + // Number of times updateRect has been invoked. + int updateCount() const { return m_updateCount; } + void clearUpdateCount() { m_updateCount = 0; } + void updateRect() { m_updateCount++; } + + // Number of times prepareRect() has been invoked on a texture. + int prepareRectCount() const { return m_prepareRectCount; } + void clearPrepareRectCount() { m_prepareRectCount = 0; } + void prepareRect() { m_prepareRectCount++; } + + void setOpaquePaintRect(const WebCore::IntRect& opaquePaintRect) { m_opaquePaintRect = opaquePaintRect; } + +private: + int m_prepareCount; + int m_updateCount; + int m_prepareRectCount; + WebCore::IntRect m_rectToInvalidate; + WebCore::IntRect m_lastUpdateRect; + WebCore::IntRect m_opaquePaintRect; + RefPtr<FakeTiledLayerChromium> m_layer; +}; + +class FakeCCTiledLayerImpl : public WebCore::CCTiledLayerImpl { +public: + explicit FakeCCTiledLayerImpl(int id); + virtual ~FakeCCTiledLayerImpl(); + + using WebCore::CCTiledLayerImpl::hasTileAt; + using WebCore::CCTiledLayerImpl::hasTextureIdForTileAt; +}; + +class FakeTiledLayerChromium : public WebCore::TiledLayerChromium { +public: + explicit FakeTiledLayerChromium(WebCore::CCPrioritizedTextureManager*); + virtual ~FakeTiledLayerChromium(); + + static WebCore::IntSize tileSize() { return WebCore::IntSize(100, 100); } + + using WebCore::TiledLayerChromium::invalidateContentRect; + using WebCore::TiledLayerChromium::needsIdlePaint; + using WebCore::TiledLayerChromium::skipsDraw; + using WebCore::TiledLayerChromium::numPaintedTiles; + using WebCore::TiledLayerChromium::idlePaintRect; + + virtual void setNeedsDisplayRect(const WebCore::FloatRect&) OVERRIDE; + const WebCore::FloatRect& lastNeedsDisplayRect() const { return m_lastNeedsDisplayRect; } + + virtual void setTexturePriorities(const WebCore::CCPriorityCalculator&) OVERRIDE; + + virtual WebCore::CCPrioritizedTextureManager* textureManager() const OVERRIDE { return m_textureManager; } + FakeLayerTextureUpdater* fakeLayerTextureUpdater() { return m_fakeTextureUpdater.get(); } + WebCore::FloatRect updateRect() { return m_updateRect; } + +protected: + virtual WebCore::LayerTextureUpdater* textureUpdater() const OVERRIDE { return m_fakeTextureUpdater.get(); } + virtual void createTextureUpdaterIfNeeded() OVERRIDE { } + +private: + RefPtr<FakeLayerTextureUpdater> m_fakeTextureUpdater; + WebCore::CCPrioritizedTextureManager* m_textureManager; + WebCore::FloatRect m_lastNeedsDisplayRect; +}; + +class FakeTiledLayerWithScaledBounds : public FakeTiledLayerChromium { +public: + explicit FakeTiledLayerWithScaledBounds(WebCore::CCPrioritizedTextureManager*); + + void setContentBounds(const WebCore::IntSize& contentBounds) { m_forcedContentBounds = contentBounds; } + virtual WebCore::IntSize contentBounds() const OVERRIDE { return m_forcedContentBounds; } + +protected: + WebCore::IntSize m_forcedContentBounds; +}; + +class FakeTextureCopier : public WebCore::TextureCopier { +public: + virtual void copyTexture(Parameters) { } + virtual void flush() { } +}; + +class FakeTextureUploader : public WebCore::TextureUploader { +public: + virtual bool isBusy() { return false; } + virtual void beginUploads() { } + virtual void endUploads() { } + virtual void uploadTexture(WebCore::CCResourceProvider* resourceProvider, Parameters upload) { upload.texture->updateRect(resourceProvider, upload.sourceRect, upload.destOffset); } +}; + +} +#endif // CCTiledLayerTestCommon_h diff --git a/cc/test/CompositorFakeWebGraphicsContext3D.h b/cc/test/CompositorFakeWebGraphicsContext3D.h new file mode 100644 index 0000000..2a709a2 --- /dev/null +++ b/cc/test/CompositorFakeWebGraphicsContext3D.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef CompositorFakeWebGraphicsContext3D_h +#define CompositorFakeWebGraphicsContext3D_h + +#include "FakeWebGraphicsContext3D.h" +#include <wtf/PassOwnPtr.h> + +namespace WebKit { + +// Test stub for WebGraphicsContext3D. Returns canned values needed for compositor initialization. +class CompositorFakeWebGraphicsContext3D : public FakeWebGraphicsContext3D { +public: + static PassOwnPtr<CompositorFakeWebGraphicsContext3D> create(Attributes attrs) + { + return adoptPtr(new CompositorFakeWebGraphicsContext3D(attrs)); + } + + virtual bool makeContextCurrent() { return true; } + virtual WebGLId createProgram() { return 1; } + virtual WebGLId createShader(WGC3Denum) { return 1; } + virtual void getShaderiv(WebGLId, WGC3Denum, WGC3Dint* value) { *value = 1; } + virtual void getProgramiv(WebGLId, WGC3Denum, WGC3Dint* value) { *value = 1; } + +protected: + explicit CompositorFakeWebGraphicsContext3D(Attributes attrs) + { + m_attrs = attrs; + } +}; + +} + +#endif diff --git a/cc/test/FakeCCGraphicsContext.h b/cc/test/FakeCCGraphicsContext.h new file mode 100644 index 0000000..0185eb3 --- /dev/null +++ b/cc/test/FakeCCGraphicsContext.h @@ -0,0 +1,22 @@ +// Copyright 2012 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. + +#ifndef FakeCCGraphicsContext_h +#define FakeCCGraphicsContext_h + +#include "CCGraphicsContext.h" +#include "CompositorFakeWebGraphicsContext3D.h" +#include "FakeWebCompositorOutputSurface.h" +#include <public/WebCompositorOutputSurface.h> + +namespace WebKit { + +static inline PassOwnPtr<WebCore::CCGraphicsContext> createFakeCCGraphicsContext() +{ + return FakeWebCompositorOutputSurface::create(CompositorFakeWebGraphicsContext3D::create(WebGraphicsContext3D::Attributes())); +} + +} // namespace WebKit + +#endif // FakeCCGraphicsContext_h diff --git a/cc/test/FakeCCLayerTreeHostClient.h b/cc/test/FakeCCLayerTreeHostClient.h new file mode 100644 index 0000000..1334d8b --- /dev/null +++ b/cc/test/FakeCCLayerTreeHostClient.h @@ -0,0 +1,39 @@ +// Copyright 2012 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. +#ifndef FakeCCLayerTreeHostClient_h +#define FakeCCLayerTreeHostClient_h + +#include "config.h" + +#include "CCLayerTreeHost.h" +#include "CompositorFakeWebGraphicsContext3D.h" +#include "FakeWebCompositorOutputSurface.h" + +namespace WebCore { + +class FakeCCLayerTreeHostClient : public CCLayerTreeHostClient { +public: + virtual void willBeginFrame() OVERRIDE { } + virtual void didBeginFrame() OVERRIDE { } + virtual void animate(double monotonicFrameBeginTime) OVERRIDE { } + virtual void layout() OVERRIDE { } + virtual void applyScrollAndScale(const IntSize& scrollDelta, float pageScale) OVERRIDE { } + + virtual PassOwnPtr<WebKit::WebCompositorOutputSurface> createOutputSurface() OVERRIDE + { + WebKit::WebGraphicsContext3D::Attributes attrs; + return WebKit::FakeWebCompositorOutputSurface::create(WebKit::CompositorFakeWebGraphicsContext3D::create(attrs)); + } + virtual void didRecreateOutputSurface(bool success) OVERRIDE { } + virtual void willCommit() OVERRIDE { } + virtual void didCommit() OVERRIDE { } + virtual void didCommitAndDrawFrame() OVERRIDE { } + virtual void didCompleteSwapBuffers() OVERRIDE { } + + // Used only in the single-threaded path. + virtual void scheduleComposite() OVERRIDE { } +}; + +} +#endif // FakeCCLayerTreeHostClient_h diff --git a/cc/test/FakeGraphicsContext3DTest.cpp b/cc/test/FakeGraphicsContext3DTest.cpp new file mode 100644 index 0000000..011da56 --- /dev/null +++ b/cc/test/FakeGraphicsContext3DTest.cpp @@ -0,0 +1,36 @@ +// 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 "config.h" + +#include "FakeWebGraphicsContext3D.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +using namespace WebCore; +using namespace WebKit; + +class ContextThatCountsMakeCurrents : public FakeWebGraphicsContext3D { +public: + ContextThatCountsMakeCurrents() : m_makeCurrentCount(0) { } + virtual bool makeContextCurrent() + { + m_makeCurrentCount++; + return true; + } + int makeCurrentCount() { return m_makeCurrentCount; } +private: + int m_makeCurrentCount; +}; + + +TEST(FakeGraphicsContext3DTest, ContextCreationShouldNotMakeCurrent) +{ + OwnPtr<ContextThatCountsMakeCurrents> context(adoptPtr(new ContextThatCountsMakeCurrents)); + EXPECT_TRUE(context); + EXPECT_EQ(0, context->makeCurrentCount()); +} + diff --git a/cc/test/FakeWebCompositorOutputSurface.h b/cc/test/FakeWebCompositorOutputSurface.h new file mode 100644 index 0000000..6a728fe --- /dev/null +++ b/cc/test/FakeWebCompositorOutputSurface.h @@ -0,0 +1,57 @@ +// Copyright 2012 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. + +#ifndef FakeWebCompositorOutputSurface_h +#define FakeWebCompositorOutputSurface_h + +#include <public/WebCompositorOutputSurface.h> +#include <public/WebGraphicsContext3D.h> + +namespace WebKit { + +class FakeWebCompositorOutputSurface : public WebCompositorOutputSurface { +public: + static inline PassOwnPtr<FakeWebCompositorOutputSurface> create(PassOwnPtr<WebGraphicsContext3D> context3D) + { + return adoptPtr(new FakeWebCompositorOutputSurface(context3D)); + } + + + virtual bool bindToClient(WebCompositorOutputSurfaceClient* client) OVERRIDE + { + ASSERT(client); + if (!m_context3D->makeContextCurrent()) + return false; + m_client = client; + return true; + } + + virtual const Capabilities& capabilities() const OVERRIDE + { + return m_capabilities; + } + + virtual WebGraphicsContext3D* context3D() const OVERRIDE + { + return m_context3D.get(); + } + + virtual void sendFrameToParentCompositor(const WebCompositorFrame&) OVERRIDE + { + } + +private: + explicit FakeWebCompositorOutputSurface(PassOwnPtr<WebGraphicsContext3D> context3D) + { + m_context3D = context3D; + } + + OwnPtr<WebGraphicsContext3D> m_context3D; + Capabilities m_capabilities; + WebCompositorOutputSurfaceClient* m_client; +}; + +} // namespace WebKit + +#endif // FakeWebCompositorOutputSurface_h diff --git a/cc/test/FakeWebGraphicsContext3D.h b/cc/test/FakeWebGraphicsContext3D.h new file mode 100644 index 0000000..67ad922 --- /dev/null +++ b/cc/test/FakeWebGraphicsContext3D.h @@ -0,0 +1,260 @@ +// 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. + +#ifndef FakeWebGraphicsContext3D_h +#define FakeWebGraphicsContext3D_h + +#include "GraphicsContext3D.h" +#include <public/WebGraphicsContext3D.h> + +namespace WebKit { + +// WebGraphicsContext3D base class for use in WebKit unit tests. +// All operations are no-ops (returning 0 if necessary). +class FakeWebGraphicsContext3D : public WebGraphicsContext3D { +public: + FakeWebGraphicsContext3D() + : m_nextTextureId(1) + { + } + + virtual bool makeContextCurrent() { return true; } + + virtual int width() { return 0; } + virtual int height() { return 0; } + + virtual void reshape(int width, int height) { } + + virtual bool isGLES2Compliant() { return false; } + + virtual bool readBackFramebuffer(unsigned char* pixels, size_t bufferSize, WebGLId framebuffer, int width, int height) { return false; } + + virtual WebGLId getPlatformTextureId() { return 0; } + + virtual void prepareTexture() { } + + virtual void postSubBufferCHROMIUM(int x, int y, int width, int height) { } + + virtual void synthesizeGLError(WGC3Denum) { } + + virtual bool isContextLost() { return false; } + + virtual void* mapBufferSubDataCHROMIUM(WGC3Denum target, WGC3Dintptr offset, WGC3Dsizeiptr size, WGC3Denum access) { return 0; } + virtual void unmapBufferSubDataCHROMIUM(const void*) { } + virtual void* mapTexSubImage2DCHROMIUM(WGC3Denum target, WGC3Dint level, WGC3Dint xoffset, WGC3Dint yoffset, WGC3Dsizei width, WGC3Dsizei height, WGC3Denum format, WGC3Denum type, WGC3Denum access) { return 0; } + virtual void unmapTexSubImage2DCHROMIUM(const void*) { } + + virtual void setVisibilityCHROMIUM(bool visible) { } + + virtual void discardFramebufferEXT(WGC3Denum target, WGC3Dsizei numAttachments, const WGC3Denum* attachments) { } + virtual void ensureFramebufferCHROMIUM() { } + + virtual void setMemoryAllocationChangedCallbackCHROMIUM(WebGraphicsMemoryAllocationChangedCallbackCHROMIUM* callback) { } + + virtual WebString getRequestableExtensionsCHROMIUM() { return WebString(); } + virtual void requestExtensionCHROMIUM(const char*) { } + + virtual void blitFramebufferCHROMIUM(WGC3Dint srcX0, WGC3Dint srcY0, WGC3Dint srcX1, WGC3Dint srcY1, WGC3Dint dstX0, WGC3Dint dstY0, WGC3Dint dstX1, WGC3Dint dstY1, WGC3Dbitfield mask, WGC3Denum filter) { } + virtual void renderbufferStorageMultisampleCHROMIUM(WGC3Denum target, WGC3Dsizei samples, WGC3Denum internalformat, WGC3Dsizei width, WGC3Dsizei height) { } + + virtual void activeTexture(WGC3Denum texture) { } + virtual void attachShader(WebGLId program, WebGLId shader) { } + virtual void bindAttribLocation(WebGLId program, WGC3Duint index, const WGC3Dchar* name) { } + virtual void bindBuffer(WGC3Denum target, WebGLId buffer) { } + virtual void bindFramebuffer(WGC3Denum target, WebGLId framebuffer) { } + virtual void bindRenderbuffer(WGC3Denum target, WebGLId renderbuffer) { } + virtual void bindTexture(WGC3Denum target, WebGLId texture) { } + virtual void blendColor(WGC3Dclampf red, WGC3Dclampf green, WGC3Dclampf blue, WGC3Dclampf alpha) { } + virtual void blendEquation(WGC3Denum mode) { } + virtual void blendEquationSeparate(WGC3Denum modeRGB, WGC3Denum modeAlpha) { } + virtual void blendFunc(WGC3Denum sfactor, WGC3Denum dfactor) { } + virtual void blendFuncSeparate(WGC3Denum srcRGB, WGC3Denum dstRGB, WGC3Denum srcAlpha, WGC3Denum dstAlpha) { } + + virtual void bufferData(WGC3Denum target, WGC3Dsizeiptr size, const void* data, WGC3Denum usage) { } + virtual void bufferSubData(WGC3Denum target, WGC3Dintptr offset, WGC3Dsizeiptr size, const void* data) { } + + virtual WGC3Denum checkFramebufferStatus(WGC3Denum target) + { + return WebCore::GraphicsContext3D::FRAMEBUFFER_COMPLETE; + } + + virtual void clear(WGC3Dbitfield mask) { } + virtual void clearColor(WGC3Dclampf red, WGC3Dclampf green, WGC3Dclampf blue, WGC3Dclampf alpha) { } + virtual void clearDepth(WGC3Dclampf depth) { } + virtual void clearStencil(WGC3Dint s) { } + virtual void colorMask(WGC3Dboolean red, WGC3Dboolean green, WGC3Dboolean blue, WGC3Dboolean alpha) { } + virtual void compileShader(WebGLId shader) { } + + virtual void compressedTexImage2D(WGC3Denum target, WGC3Dint level, WGC3Denum internalformat, WGC3Dsizei width, WGC3Dsizei height, WGC3Dint border, WGC3Dsizei imageSize, const void* data) { } + virtual void compressedTexSubImage2D(WGC3Denum target, WGC3Dint level, WGC3Dint xoffset, WGC3Dint yoffset, WGC3Dsizei width, WGC3Dsizei height, WGC3Denum format, WGC3Dsizei imageSize, const void* data) { } + virtual void copyTexImage2D(WGC3Denum target, WGC3Dint level, WGC3Denum internalformat, WGC3Dint x, WGC3Dint y, WGC3Dsizei width, WGC3Dsizei height, WGC3Dint border) { } + virtual void copyTexSubImage2D(WGC3Denum target, WGC3Dint level, WGC3Dint xoffset, WGC3Dint yoffset, WGC3Dint x, WGC3Dint y, WGC3Dsizei width, WGC3Dsizei height) { } + virtual void cullFace(WGC3Denum mode) { } + virtual void depthFunc(WGC3Denum func) { } + virtual void depthMask(WGC3Dboolean flag) { } + virtual void depthRange(WGC3Dclampf zNear, WGC3Dclampf zFar) { } + virtual void detachShader(WebGLId program, WebGLId shader) { } + virtual void disable(WGC3Denum cap) { } + virtual void disableVertexAttribArray(WGC3Duint index) { } + virtual void drawArrays(WGC3Denum mode, WGC3Dint first, WGC3Dsizei count) { } + virtual void drawElements(WGC3Denum mode, WGC3Dsizei count, WGC3Denum type, WGC3Dintptr offset) { } + + virtual void enable(WGC3Denum cap) { } + virtual void enableVertexAttribArray(WGC3Duint index) { } + virtual void finish() { } + virtual void flush() { } + virtual void framebufferRenderbuffer(WGC3Denum target, WGC3Denum attachment, WGC3Denum renderbuffertarget, WebGLId renderbuffer) { } + virtual void framebufferTexture2D(WGC3Denum target, WGC3Denum attachment, WGC3Denum textarget, WebGLId texture, WGC3Dint level) { } + virtual void frontFace(WGC3Denum mode) { } + virtual void generateMipmap(WGC3Denum target) { } + + virtual bool getActiveAttrib(WebGLId program, WGC3Duint index, ActiveInfo&) { return false; } + virtual bool getActiveUniform(WebGLId program, WGC3Duint index, ActiveInfo&) { return false; } + virtual void getAttachedShaders(WebGLId program, WGC3Dsizei maxCount, WGC3Dsizei* count, WebGLId* shaders) { } + virtual WGC3Dint getAttribLocation(WebGLId program, const WGC3Dchar* name) { return 0; } + virtual void getBooleanv(WGC3Denum pname, WGC3Dboolean* value) { } + virtual void getBufferParameteriv(WGC3Denum target, WGC3Denum pname, WGC3Dint* value) { } + virtual Attributes getContextAttributes() { return m_attrs; } + virtual WGC3Denum getError() { return 0; } + virtual void getFloatv(WGC3Denum pname, WGC3Dfloat* value) { } + virtual void getFramebufferAttachmentParameteriv(WGC3Denum target, WGC3Denum attachment, WGC3Denum pname, WGC3Dint* value) { } + + virtual void getIntegerv(WGC3Denum pname, WGC3Dint* value) + { + if (pname == WebCore::GraphicsContext3D::MAX_TEXTURE_SIZE) + *value = 1024; + } + + virtual void getProgramiv(WebGLId program, WGC3Denum pname, WGC3Dint* value) + { + if (pname == WebCore::GraphicsContext3D::LINK_STATUS) + *value = 1; + } + + virtual WebString getProgramInfoLog(WebGLId program) { return WebString(); } + virtual void getRenderbufferParameteriv(WGC3Denum target, WGC3Denum pname, WGC3Dint* value) { } + + virtual void getShaderiv(WebGLId shader, WGC3Denum pname, WGC3Dint* value) + { + if (pname == WebCore::GraphicsContext3D::COMPILE_STATUS) + *value = 1; + } + + virtual WebString getShaderInfoLog(WebGLId shader) { return WebString(); } + virtual void getShaderPrecisionFormat(WGC3Denum shadertype, WGC3Denum precisiontype, WGC3Dint* range, WGC3Dint* precision) { } + virtual WebString getShaderSource(WebGLId shader) { return WebString(); } + virtual WebString getString(WGC3Denum name) { return WebString(); } + virtual void getTexParameterfv(WGC3Denum target, WGC3Denum pname, WGC3Dfloat* value) { } + virtual void getTexParameteriv(WGC3Denum target, WGC3Denum pname, WGC3Dint* value) { } + virtual void getUniformfv(WebGLId program, WGC3Dint location, WGC3Dfloat* value) { } + virtual void getUniformiv(WebGLId program, WGC3Dint location, WGC3Dint* value) { } + virtual WGC3Dint getUniformLocation(WebGLId program, const WGC3Dchar* name) { return 0; } + virtual void getVertexAttribfv(WGC3Duint index, WGC3Denum pname, WGC3Dfloat* value) { } + virtual void getVertexAttribiv(WGC3Duint index, WGC3Denum pname, WGC3Dint* value) { } + virtual WGC3Dsizeiptr getVertexAttribOffset(WGC3Duint index, WGC3Denum pname) { return 0; } + + virtual void hint(WGC3Denum target, WGC3Denum mode) { } + virtual WGC3Dboolean isBuffer(WebGLId buffer) { return false; } + virtual WGC3Dboolean isEnabled(WGC3Denum cap) { return false; } + virtual WGC3Dboolean isFramebuffer(WebGLId framebuffer) { return false; } + virtual WGC3Dboolean isProgram(WebGLId program) { return false; } + virtual WGC3Dboolean isRenderbuffer(WebGLId renderbuffer) { return false; } + virtual WGC3Dboolean isShader(WebGLId shader) { return false; } + virtual WGC3Dboolean isTexture(WebGLId texture) { return false; } + virtual void lineWidth(WGC3Dfloat) { } + virtual void linkProgram(WebGLId program) { } + virtual void pixelStorei(WGC3Denum pname, WGC3Dint param) { } + virtual void polygonOffset(WGC3Dfloat factor, WGC3Dfloat units) { } + + virtual void readPixels(WGC3Dint x, WGC3Dint y, WGC3Dsizei width, WGC3Dsizei height, WGC3Denum format, WGC3Denum type, void* pixels) { } + + virtual void releaseShaderCompiler() { } + + virtual void renderbufferStorage(WGC3Denum target, WGC3Denum internalformat, WGC3Dsizei width, WGC3Dsizei height) { } + virtual void sampleCoverage(WGC3Dclampf value, WGC3Dboolean invert) { } + virtual void scissor(WGC3Dint x, WGC3Dint y, WGC3Dsizei width, WGC3Dsizei height) { } + virtual void shaderSource(WebGLId shader, const WGC3Dchar* string) { } + virtual void stencilFunc(WGC3Denum func, WGC3Dint ref, WGC3Duint mask) { } + virtual void stencilFuncSeparate(WGC3Denum face, WGC3Denum func, WGC3Dint ref, WGC3Duint mask) { } + virtual void stencilMask(WGC3Duint mask) { } + virtual void stencilMaskSeparate(WGC3Denum face, WGC3Duint mask) { } + virtual void stencilOp(WGC3Denum fail, WGC3Denum zfail, WGC3Denum zpass) { } + virtual void stencilOpSeparate(WGC3Denum face, WGC3Denum fail, WGC3Denum zfail, WGC3Denum zpass) { } + + virtual void texImage2D(WGC3Denum target, WGC3Dint level, WGC3Denum internalformat, WGC3Dsizei width, WGC3Dsizei height, WGC3Dint border, WGC3Denum format, WGC3Denum type, const void* pixels) { } + + virtual void texParameterf(WGC3Denum target, WGC3Denum pname, WGC3Dfloat param) { } + virtual void texParameteri(WGC3Denum target, WGC3Denum pname, WGC3Dint param) { } + + virtual void texSubImage2D(WGC3Denum target, WGC3Dint level, WGC3Dint xoffset, WGC3Dint yoffset, WGC3Dsizei width, WGC3Dsizei height, WGC3Denum format, WGC3Denum type, const void* pixels) { } + + virtual void uniform1f(WGC3Dint location, WGC3Dfloat x) { } + virtual void uniform1fv(WGC3Dint location, WGC3Dsizei count, const WGC3Dfloat* v) { } + virtual void uniform1i(WGC3Dint location, WGC3Dint x) { } + virtual void uniform1iv(WGC3Dint location, WGC3Dsizei count, const WGC3Dint* v) { } + virtual void uniform2f(WGC3Dint location, WGC3Dfloat x, WGC3Dfloat y) { } + virtual void uniform2fv(WGC3Dint location, WGC3Dsizei count, const WGC3Dfloat* v) { } + virtual void uniform2i(WGC3Dint location, WGC3Dint x, WGC3Dint y) { } + virtual void uniform2iv(WGC3Dint location, WGC3Dsizei count, const WGC3Dint* v) { } + virtual void uniform3f(WGC3Dint location, WGC3Dfloat x, WGC3Dfloat y, WGC3Dfloat z) { } + virtual void uniform3fv(WGC3Dint location, WGC3Dsizei count, const WGC3Dfloat* v) { } + virtual void uniform3i(WGC3Dint location, WGC3Dint x, WGC3Dint y, WGC3Dint z) { } + virtual void uniform3iv(WGC3Dint location, WGC3Dsizei count, const WGC3Dint* v) { } + virtual void uniform4f(WGC3Dint location, WGC3Dfloat x, WGC3Dfloat y, WGC3Dfloat z, WGC3Dfloat w) { } + virtual void uniform4fv(WGC3Dint location, WGC3Dsizei count, const WGC3Dfloat* v) { } + virtual void uniform4i(WGC3Dint location, WGC3Dint x, WGC3Dint y, WGC3Dint z, WGC3Dint w) { } + virtual void uniform4iv(WGC3Dint location, WGC3Dsizei count, const WGC3Dint* v) { } + virtual void uniformMatrix2fv(WGC3Dint location, WGC3Dsizei count, WGC3Dboolean transpose, const WGC3Dfloat* value) { } + virtual void uniformMatrix3fv(WGC3Dint location, WGC3Dsizei count, WGC3Dboolean transpose, const WGC3Dfloat* value) { } + virtual void uniformMatrix4fv(WGC3Dint location, WGC3Dsizei count, WGC3Dboolean transpose, const WGC3Dfloat* value) { } + + virtual void useProgram(WebGLId program) { } + virtual void validateProgram(WebGLId program) { } + + virtual void vertexAttrib1f(WGC3Duint index, WGC3Dfloat x) { } + virtual void vertexAttrib1fv(WGC3Duint index, const WGC3Dfloat* values) { } + virtual void vertexAttrib2f(WGC3Duint index, WGC3Dfloat x, WGC3Dfloat y) { } + virtual void vertexAttrib2fv(WGC3Duint index, const WGC3Dfloat* values) { } + virtual void vertexAttrib3f(WGC3Duint index, WGC3Dfloat x, WGC3Dfloat y, WGC3Dfloat z) { } + virtual void vertexAttrib3fv(WGC3Duint index, const WGC3Dfloat* values) { } + virtual void vertexAttrib4f(WGC3Duint index, WGC3Dfloat x, WGC3Dfloat y, WGC3Dfloat z, WGC3Dfloat w) { } + virtual void vertexAttrib4fv(WGC3Duint index, const WGC3Dfloat* values) { } + virtual void vertexAttribPointer(WGC3Duint index, WGC3Dint size, WGC3Denum type, WGC3Dboolean normalized, + WGC3Dsizei stride, WGC3Dintptr offset) { } + + virtual void viewport(WGC3Dint x, WGC3Dint y, WGC3Dsizei width, WGC3Dsizei height) { } + + virtual WebGLId createBuffer() { return 1; } + virtual WebGLId createFramebuffer() { return 1; } + virtual WebGLId createProgram() { return 1; } + virtual WebGLId createRenderbuffer() { return 1; } + virtual WebGLId createShader(WGC3Denum) { return 1; } + virtual WebGLId createTexture() { return m_nextTextureId++; } + + virtual void deleteBuffer(WebGLId) { } + virtual void deleteFramebuffer(WebGLId) { } + virtual void deleteProgram(WebGLId) { } + virtual void deleteRenderbuffer(WebGLId) { } + virtual void deleteShader(WebGLId) { } + virtual void deleteTexture(WebGLId) { } + + virtual void texStorage2DEXT(WGC3Denum target, WGC3Dint levels, WGC3Duint internalformat, + WGC3Dint width, WGC3Dint height) { } + + virtual WebGLId createQueryEXT() { return 1; } + virtual void deleteQueryEXT(WebGLId) { } + virtual GC3Dboolean isQueryEXT(WebGLId) { return true; } + virtual void beginQueryEXT(GC3Denum, WebGLId) { } + virtual void endQueryEXT(GC3Denum) { } + virtual void getQueryivEXT(GC3Denum, GC3Denum, GC3Dint*) { } + virtual void getQueryObjectuivEXT(WebGLId, GC3Denum, GC3Duint*) { } + +protected: + unsigned m_nextTextureId; + Attributes m_attrs; +}; + +} // namespace WebKit + +#endif // FakeWebGraphicsContext3D_h diff --git a/cc/test/FakeWebScrollbarThemeGeometry.h b/cc/test/FakeWebScrollbarThemeGeometry.h new file mode 100644 index 0000000..640da26 --- /dev/null +++ b/cc/test/FakeWebScrollbarThemeGeometry.h @@ -0,0 +1,48 @@ +// Copyright 2012 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. + +#ifndef FakeWebScrollbarThemeGeometry_h +#define FakeWebScrollbarThemeGeometry_h + +#include <public/WebScrollbarThemeGeometry.h> +#include <wtf/PassOwnPtr.h> + +namespace WebKit { + +class FakeWebScrollbarThemeGeometry : public WebKit::WebScrollbarThemeGeometry { +public: + static PassOwnPtr<WebKit::WebScrollbarThemeGeometry> create() { return adoptPtr(new WebKit::FakeWebScrollbarThemeGeometry()); } + + virtual WebKit::WebScrollbarThemeGeometry* clone() const OVERRIDE + { + return new FakeWebScrollbarThemeGeometry(); + } + + virtual int thumbPosition(WebScrollbar*) OVERRIDE { return 0; } + virtual int thumbLength(WebScrollbar*) OVERRIDE { return 0; } + virtual int trackPosition(WebScrollbar*) OVERRIDE { return 0; } + virtual int trackLength(WebScrollbar*) OVERRIDE { return 0; } + virtual bool hasButtons(WebScrollbar*) OVERRIDE { return false; } + virtual bool hasThumb(WebScrollbar*) OVERRIDE { return false; } + virtual WebRect trackRect(WebScrollbar*) OVERRIDE { return WebRect(); } + virtual WebRect thumbRect(WebScrollbar*) OVERRIDE { return WebRect(); } + virtual int minimumThumbLength(WebScrollbar*) OVERRIDE { return 0; } + virtual int scrollbarThickness(WebScrollbar*) OVERRIDE { return 0; } + virtual WebRect backButtonStartRect(WebScrollbar*) OVERRIDE { return WebRect(); } + virtual WebRect backButtonEndRect(WebScrollbar*) OVERRIDE { return WebRect(); } + virtual WebRect forwardButtonStartRect(WebScrollbar*) OVERRIDE { return WebRect(); } + virtual WebRect forwardButtonEndRect(WebScrollbar*) OVERRIDE { return WebRect(); } + virtual WebRect constrainTrackRectToTrackPieces(WebScrollbar*, const WebRect&) OVERRIDE { return WebRect(); } + + virtual void splitTrack(WebScrollbar*, const WebRect& track, WebRect& startTrack, WebRect& thumb, WebRect& endTrack) OVERRIDE + { + startTrack = WebRect(); + thumb = WebRect(); + endTrack = WebRect(); + } +}; + +} // namespace WebKit + +#endif // FakeWebScrollbarThemeGeometry_h diff --git a/cc/test/MockCCQuadCuller.h b/cc/test/MockCCQuadCuller.h new file mode 100644 index 0000000..56288d4 --- /dev/null +++ b/cc/test/MockCCQuadCuller.h @@ -0,0 +1,58 @@ +// Copyright 2012 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. + +#ifndef MockCCQuadCuller_h +#define MockCCQuadCuller_h + +#include "CCDrawQuad.h" +#include "CCQuadSink.h" +#include "IntRect.h" +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class MockCCQuadCuller : public CCQuadSink { +public: + MockCCQuadCuller() + : m_activeQuadList(m_quadListStorage) + , m_activeSharedQuadStateList(m_sharedQuadStateStorage) + { } + + explicit MockCCQuadCuller(CCQuadList& externalQuadList, CCSharedQuadStateList& externalSharedQuadStateList) + : m_activeQuadList(externalQuadList) + , m_activeSharedQuadStateList(externalSharedQuadStateList) + { } + + virtual bool append(WTF::PassOwnPtr<CCDrawQuad> newQuad) OVERRIDE + { + OwnPtr<CCDrawQuad> drawQuad = newQuad; + if (!drawQuad->quadRect().isEmpty()) { + m_activeQuadList.append(drawQuad.release()); + return true; + } + return false; + } + + virtual CCSharedQuadState* useSharedQuadState(PassOwnPtr<CCSharedQuadState> passSharedQuadState) OVERRIDE + { + OwnPtr<CCSharedQuadState> sharedQuadState(passSharedQuadState); + sharedQuadState->id = m_activeSharedQuadStateList.size(); + + CCSharedQuadState* rawPtr = sharedQuadState.get(); + m_activeSharedQuadStateList.append(sharedQuadState.release()); + return rawPtr; + } + + const CCQuadList& quadList() const { return m_activeQuadList; }; + const CCSharedQuadStateList& sharedQuadStateList() const { return m_activeSharedQuadStateList; }; + +private: + CCQuadList& m_activeQuadList; + CCQuadList m_quadListStorage; + CCSharedQuadStateList& m_activeSharedQuadStateList; + CCSharedQuadStateList m_sharedQuadStateStorage; +}; + +} // namespace WebCore +#endif // MockCCQuadCuller_h diff --git a/cc/test/run_all_unittests.cc b/cc/test/run_all_unittests.cc new file mode 100644 index 0000000..de5b1c6 --- /dev/null +++ b/cc/test/run_all_unittests.cc @@ -0,0 +1,18 @@ +// Copyright (c) 2012 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 <base/test/test_suite.h> +#include <gmock/gmock.h> +#include <webkit/support/webkit_support.h> + +int main(int argc, char** argv) { + ::testing::InitGoogleMock(&argc, argv); + TestSuite testSuite(argc, argv); + webkit_support::SetUpTestEnvironmentForUnitTests(); + int result = testSuite.Run(); + webkit_support::TearDownTestEnvironment(); + + return result; +} + diff --git a/webkit/compositor/CCThreadImpl.cpp b/webkit/compositor/CCThreadImpl.cpp new file mode 100644 index 0000000..18db0ad --- /dev/null +++ b/webkit/compositor/CCThreadImpl.cpp @@ -0,0 +1,98 @@ +// 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 "config.h" +#include "CCThreadImpl.h" + +#include "CCCompletionEvent.h" +#include <public/Platform.h> +#include <public/WebThread.h> + +using WebCore::CCThread; +using WebCore::CCCompletionEvent; + +namespace WebKit { + +// Task that, when runs, places the current thread ID into the provided +// pointer and signals a completion event. +// +// Does not provide a PassOwnPtr<GetThreadIDTask>::create method because +// the resulting object is always handed into a WebThread, which does not understand +// PassOwnPtrs. +class GetThreadIDTask : public WebThread::Task { +public: + GetThreadIDTask(ThreadIdentifier* result, CCCompletionEvent* completion) + : m_completion(completion) + , m_result(result) { } + + virtual ~GetThreadIDTask() { } + + virtual void run() + { + *m_result = currentThread(); + m_completion->signal(); + } + +private: + CCCompletionEvent* m_completion; + ThreadIdentifier* m_result; +}; + +// General adapter from a CCThread::Task to a WebThread::Task. +class CCThreadTaskAdapter : public WebThread::Task { +public: + CCThreadTaskAdapter(PassOwnPtr<CCThread::Task> task) : m_task(task) { } + + virtual ~CCThreadTaskAdapter() { } + + virtual void run() + { + m_task->performTask(); + } + +private: + OwnPtr<CCThread::Task> m_task; +}; + +PassOwnPtr<CCThread> CCThreadImpl::create(WebThread* thread) +{ + return adoptPtr(new CCThreadImpl(thread)); +} + +CCThreadImpl::~CCThreadImpl() +{ +} + +void CCThreadImpl::postTask(PassOwnPtr<CCThread::Task> task) +{ + m_thread->postTask(new CCThreadTaskAdapter(task)); +} + +void CCThreadImpl::postDelayedTask(PassOwnPtr<CCThread::Task> task, long long delayMs) +{ + m_thread->postDelayedTask(new CCThreadTaskAdapter(task), delayMs); +} + +ThreadIdentifier CCThreadImpl::threadID() const +{ + return m_threadID; +} + +CCThreadImpl::CCThreadImpl(WebThread* thread) + : m_thread(thread) +{ + if (thread == WebKit::Platform::current()->currentThread()) { + m_threadID = currentThread(); + return; + } + + // Get the threadId for the newly-created thread by running a task + // on that thread, blocking on the result. + m_threadID = currentThread(); + CCCompletionEvent completion; + m_thread->postTask(new GetThreadIDTask(&m_threadID, &completion)); + completion.wait(); +} + +} // namespace WebKit diff --git a/webkit/compositor/CCThreadImpl.h b/webkit/compositor/CCThreadImpl.h new file mode 100644 index 0000000..094d5b5 --- /dev/null +++ b/webkit/compositor/CCThreadImpl.h @@ -0,0 +1,34 @@ +// 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 "CCThread.h" +#include <wtf/OwnPtr.h> +#include <wtf/Threading.h> + +#ifndef CCThreadImpl_h +#define CCThreadImpl_h + +namespace WebKit { + +class WebThread; + +// Implements CCThread in terms of WebThread. +class CCThreadImpl : public WebCore::CCThread { +public: + static PassOwnPtr<WebCore::CCThread> create(WebThread*); + virtual ~CCThreadImpl(); + virtual void postTask(PassOwnPtr<WebCore::CCThread::Task>); + virtual void postDelayedTask(PassOwnPtr<WebCore::CCThread::Task>, long long delayMs); + WTF::ThreadIdentifier threadID() const; + +private: + explicit CCThreadImpl(WebThread*); + + WebThread* m_thread; + WTF::ThreadIdentifier m_threadID; +}; + +} // namespace WebKit + +#endif diff --git a/webkit/compositor/PlatformGestureCurve.h b/webkit/compositor/PlatformGestureCurve.h new file mode 100644 index 0000000..5669742 --- /dev/null +++ b/webkit/compositor/PlatformGestureCurve.h @@ -0,0 +1,29 @@ +// Copyright 2012 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. + +#ifndef PlatformGestureCurve_h +#define PlatformGestureCurve_h + +namespace WebCore { + +class PlatformGestureCurveTarget; + +// Abstract interface for curves used by ActivePlatformGestureAnimation. A +// PlatformGestureCurve defines the animation parameters as a function of time +// (zero-based), and applies the parameters directly to the target of the +// animation. +class PlatformGestureCurve { +public: + virtual ~PlatformGestureCurve() { } + + // Returns a name of the curve for use in debugging. + virtual const char* debugName() const = 0; + + // Returns false if curve has finished and can no longer be applied. + virtual bool apply(double time, PlatformGestureCurveTarget*) = 0; +}; + +} // namespace WebCore + +#endif diff --git a/webkit/compositor/PlatformGestureCurveTarget.h b/webkit/compositor/PlatformGestureCurveTarget.h new file mode 100644 index 0000000..41e96d6 --- /dev/null +++ b/webkit/compositor/PlatformGestureCurveTarget.h @@ -0,0 +1,23 @@ +// Copyright 2012 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. + +#ifndef PlatformGestureCurveTarget_h +#define PlatformGestureCurveTarget_h + +namespace WebCore { + +class IntPoint; + +class PlatformGestureCurveTarget { +public: + virtual void scrollBy(const IntPoint&) = 0; + // FIXME: add interfaces for scroll(), etc. + +protected: + virtual ~PlatformGestureCurveTarget() { } +}; + +} // namespace WebCore + +#endif diff --git a/webkit/compositor/TouchpadFlingPlatformGestureCurve.h b/webkit/compositor/TouchpadFlingPlatformGestureCurve.h new file mode 100644 index 0000000..072e914 --- /dev/null +++ b/webkit/compositor/TouchpadFlingPlatformGestureCurve.h @@ -0,0 +1,45 @@ +// Copyright 2012 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. + +#ifndef TouchpadFlingPlatformGestureCurve_h +#define TouchpadFlingPlatformGestureCurve_h + +#include "FloatPoint.h" +#include "PlatformGestureCurve.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class PlatformGestureCurveTarget; + +// Implementation of PlatformGestureCurve suitable for touch pad/screen-based +// fling scroll. Starts with a flat velocity profile based on 'velocity', which +// tails off to zero. Time is scaled to that duration of the fling is proportional +// the initial velocity. +class TouchpadFlingPlatformGestureCurve : public PlatformGestureCurve { +public: + static PassOwnPtr<PlatformGestureCurve> create(const FloatPoint& velocity, IntPoint cumulativeScroll = IntPoint()); + static PassOwnPtr<PlatformGestureCurve> create(const FloatPoint& velocity, float p0, float p1, float p2, float p3, float p4, float curveDuration, IntPoint cumulativeScroll = IntPoint()); + virtual ~TouchpadFlingPlatformGestureCurve(); + + virtual const char* debugName() const OVERRIDE; + virtual bool apply(double monotonicTime, PlatformGestureCurveTarget*) OVERRIDE; + +private: + TouchpadFlingPlatformGestureCurve(const FloatPoint& velocity, float p0, float p1, float p2, float p3, float p4, float curveDuration, const IntPoint& cumulativeScroll); + + FloatPoint m_displacementRatio; + IntPoint m_cumulativeScroll; + float m_coeffs[5]; + float m_timeOffset; + float m_curveDuration; + float m_positionOffset; + + static const int m_maxSearchIterations; +}; + +} // namespace WebCore + +#endif diff --git a/webkit/compositor/WebAnimationCurveCommon.cpp b/webkit/compositor/WebAnimationCurveCommon.cpp new file mode 100644 index 0000000..2e45dc5 --- /dev/null +++ b/webkit/compositor/WebAnimationCurveCommon.cpp @@ -0,0 +1,33 @@ +// Copyright 2012 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 "WebAnimationCurveCommon.h" + +#include "CCTimingFunction.h" + +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebKit { + +PassOwnPtr<WebCore::CCTimingFunction> createTimingFunction(WebAnimationCurve::TimingFunctionType type) +{ + switch (type) { + case WebAnimationCurve::TimingFunctionTypeEase: + return WebCore::CCEaseTimingFunction::create(); + case WebAnimationCurve::TimingFunctionTypeEaseIn: + return WebCore::CCEaseInTimingFunction::create(); + case WebAnimationCurve::TimingFunctionTypeEaseOut: + return WebCore::CCEaseOutTimingFunction::create(); + case WebAnimationCurve::TimingFunctionTypeEaseInOut: + return WebCore::CCEaseInOutTimingFunction::create(); + case WebAnimationCurve::TimingFunctionTypeLinear: + return nullptr; + } + return nullptr; +} + +} // namespace WebKit diff --git a/webkit/compositor/WebAnimationCurveCommon.h b/webkit/compositor/WebAnimationCurveCommon.h new file mode 100644 index 0000000..b911e82 --- /dev/null +++ b/webkit/compositor/WebAnimationCurveCommon.h @@ -0,0 +1,19 @@ +// Copyright 2012 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. + +#ifndef WebAnimationCurveCommon_h +#define WebAnimationCurveCommon_h + +#include <public/WebAnimationCurve.h> +#include <wtf/Forward.h> + +namespace WebCore { +class CCTimingFunction; +} + +namespace WebKit { +PassOwnPtr<WebCore::CCTimingFunction> createTimingFunction(WebAnimationCurve::TimingFunctionType); +} + +#endif // WebAnimationCurveCommon_h diff --git a/webkit/compositor/WebAnimationImpl.cpp b/webkit/compositor/WebAnimationImpl.cpp new file mode 100644 index 0000000..e7cc459 --- /dev/null +++ b/webkit/compositor/WebAnimationImpl.cpp @@ -0,0 +1,109 @@ +// Copyright 2012 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 "WebAnimationImpl.h" + +#include "CCActiveAnimation.h" +#include "CCAnimationCurve.h" +#include "WebFloatAnimationCurveImpl.h" +#include "WebTransformAnimationCurveImpl.h" +#include <public/WebAnimation.h> +#include <public/WebAnimationCurve.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +using WebCore::CCActiveAnimation; + +namespace WebKit { + +WebAnimation* WebAnimation::create(const WebAnimationCurve& curve, TargetProperty targetProperty, int animationId) +{ + static int nextGroupId = 1; + static int nextAnimationId = 1; + return new WebAnimationImpl(curve, targetProperty, animationId ? animationId : nextAnimationId++, nextGroupId++); +} + +WebAnimationImpl::WebAnimationImpl(const WebAnimationCurve& webCurve, TargetProperty targetProperty, int animationId, int groupId) +{ + WebAnimationCurve::AnimationCurveType curveType = webCurve.type(); + OwnPtr<WebCore::CCAnimationCurve> curve; + switch (curveType) { + case WebAnimationCurve::AnimationCurveTypeFloat: { + const WebFloatAnimationCurveImpl* floatCurveImpl = static_cast<const WebFloatAnimationCurveImpl*>(&webCurve); + curve = floatCurveImpl->cloneToCCAnimationCurve(); + break; + } + case WebAnimationCurve::AnimationCurveTypeTransform: { + const WebTransformAnimationCurveImpl* transformCurveImpl = static_cast<const WebTransformAnimationCurveImpl*>(&webCurve); + curve = transformCurveImpl->cloneToCCAnimationCurve(); + break; + } + } + m_animation = CCActiveAnimation::create(curve.release(), animationId, groupId, static_cast<WebCore::CCActiveAnimation::TargetProperty>(targetProperty)); +} + +WebAnimationImpl::~WebAnimationImpl() +{ +} + +int WebAnimationImpl::id() +{ + return m_animation->id(); +} + +WebAnimation::TargetProperty WebAnimationImpl::targetProperty() const +{ + return static_cast<WebAnimationImpl::TargetProperty>(m_animation->targetProperty()); +} + +int WebAnimationImpl::iterations() const +{ + return m_animation->iterations(); +} + +void WebAnimationImpl::setIterations(int n) +{ + m_animation->setIterations(n); +} + +double WebAnimationImpl::startTime() const +{ + return m_animation->startTime(); +} + +void WebAnimationImpl::setStartTime(double monotonicTime) +{ + m_animation->setStartTime(monotonicTime); +} + +double WebAnimationImpl::timeOffset() const +{ + return m_animation->timeOffset(); +} + +void WebAnimationImpl::setTimeOffset(double monotonicTime) +{ + m_animation->setTimeOffset(monotonicTime); +} + +bool WebAnimationImpl::alternatesDirection() const +{ + return m_animation->alternatesDirection(); +} + +void WebAnimationImpl::setAlternatesDirection(bool alternates) +{ + m_animation->setAlternatesDirection(alternates); +} + +PassOwnPtr<WebCore::CCActiveAnimation> WebAnimationImpl::cloneToCCAnimation() +{ + OwnPtr<WebCore::CCActiveAnimation> toReturn(m_animation->clone(WebCore::CCActiveAnimation::NonControllingInstance)); + toReturn->setNeedsSynchronizedStartTime(true); + return toReturn.release(); +} + +} // namespace WebKit diff --git a/webkit/compositor/WebAnimationImpl.h b/webkit/compositor/WebAnimationImpl.h new file mode 100644 index 0000000..a80fe18 --- /dev/null +++ b/webkit/compositor/WebAnimationImpl.h @@ -0,0 +1,43 @@ +// Copyright 2012 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. + +#ifndef WebAnimationImpl_h +#define WebAnimationImpl_h + +#include <public/WebAnimation.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { +class CCActiveAnimation; +} + +namespace WebKit { + +class WebAnimationImpl : public WebAnimation { +public: + WebAnimationImpl(const WebAnimationCurve&, TargetProperty, int animationId, int groupId); + virtual ~WebAnimationImpl(); + + // WebAnimation implementation + virtual int id() OVERRIDE; + virtual TargetProperty targetProperty() const OVERRIDE; + virtual int iterations() const OVERRIDE; + virtual void setIterations(int) OVERRIDE; + virtual double startTime() const OVERRIDE; + virtual void setStartTime(double monotonicTime) OVERRIDE; + virtual double timeOffset() const OVERRIDE; + virtual void setTimeOffset(double monotonicTime) OVERRIDE; + virtual bool alternatesDirection() const OVERRIDE; + virtual void setAlternatesDirection(bool) OVERRIDE; + + PassOwnPtr<WebCore::CCActiveAnimation> cloneToCCAnimation(); +private: + OwnPtr<WebCore::CCActiveAnimation> m_animation; +}; + +} + +#endif // WebAnimationImpl_h + diff --git a/webkit/compositor/WebCompositorImpl.cpp b/webkit/compositor/WebCompositorImpl.cpp new file mode 100644 index 0000000..530fa9b --- /dev/null +++ b/webkit/compositor/WebCompositorImpl.cpp @@ -0,0 +1,98 @@ +// 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 "config.h" + +#include "WebCompositorImpl.h" + +#include "CCLayerTreeHost.h" +#include "CCProxy.h" +#include "CCSettings.h" +#include "CCThreadImpl.h" +#include <public/Platform.h> +#include <wtf/ThreadingPrimitives.h> + +using namespace WebCore; + +namespace WebKit { + +bool WebCompositorImpl::s_initialized = false; +CCThread* WebCompositorImpl::s_mainThread = 0; +CCThread* WebCompositorImpl::s_implThread = 0; + +void WebCompositor::initialize(WebThread* implThread) +{ + WebCompositorImpl::initialize(implThread); +} + +bool WebCompositor::threadingEnabled() +{ + return WebCompositorImpl::threadingEnabled(); +} + +void WebCompositor::shutdown() +{ + WebCompositorImpl::shutdown(); + CCSettings::reset(); +} + +void WebCompositor::setPerTilePaintingEnabled(bool enabled) +{ + ASSERT(!WebCompositorImpl::initialized()); + CCSettings::setPerTilePaintingEnabled(enabled); +} + +void WebCompositor::setPartialSwapEnabled(bool enabled) +{ + ASSERT(!WebCompositorImpl::initialized()); + CCSettings::setPartialSwapEnabled(enabled); +} + +void WebCompositor::setAcceleratedAnimationEnabled(bool enabled) +{ + ASSERT(!WebCompositorImpl::initialized()); + CCSettings::setAcceleratedAnimationEnabled(enabled); +} + +void WebCompositorImpl::initialize(WebThread* implThread) +{ + ASSERT(!s_initialized); + s_initialized = true; + + s_mainThread = CCThreadImpl::create(WebKit::Platform::current()->currentThread()).leakPtr(); + CCProxy::setMainThread(s_mainThread); + if (implThread) { + s_implThread = CCThreadImpl::create(implThread).leakPtr(); + CCProxy::setImplThread(s_implThread); + } else + CCProxy::setImplThread(0); +} + +bool WebCompositorImpl::threadingEnabled() +{ + return s_implThread; +} + +bool WebCompositorImpl::initialized() +{ + return s_initialized; +} + +void WebCompositorImpl::shutdown() +{ + ASSERT(s_initialized); + ASSERT(!CCLayerTreeHost::anyLayerTreeHostInstanceExists()); + + if (s_implThread) { + delete s_implThread; + s_implThread = 0; + } + delete s_mainThread; + s_mainThread = 0; + CCProxy::setImplThread(0); + CCProxy::setMainThread(0); + s_initialized = false; +} + +} diff --git a/webkit/compositor/WebCompositorImpl.h b/webkit/compositor/WebCompositorImpl.h new file mode 100644 index 0000000..8c386bc --- /dev/null +++ b/webkit/compositor/WebCompositorImpl.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef WebCompositorImpl_h +#define WebCompositorImpl_h + +#include <public/WebCompositor.h> + +#include <wtf/HashSet.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { +class CCThread; +} + +namespace WebKit { + +class WebThread; + +class WebCompositorImpl : public WebCompositor { + WTF_MAKE_NONCOPYABLE(WebCompositorImpl); +public: + static bool initialized(); + +private: + + friend class WebCompositor; + static void initialize(WebThread* implThread); + static bool threadingEnabled(); + static void shutdown(); + + static bool s_initialized; + static WebCore::CCThread* s_mainThread; + static WebCore::CCThread* s_implThread; +}; + +} + +#endif // WebCompositorImpl_h diff --git a/webkit/compositor/WebCompositorInputHandlerImpl.cpp b/webkit/compositor/WebCompositorInputHandlerImpl.cpp new file mode 100644 index 0000000..20c1e96c --- /dev/null +++ b/webkit/compositor/WebCompositorInputHandlerImpl.cpp @@ -0,0 +1,341 @@ +// 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 "config.h" + +#include "WebCompositorInputHandlerImpl.h" + +#include "CCActiveGestureAnimation.h" +#include "CCProxy.h" +#include "PlatformGestureCurveTarget.h" +#include "TouchpadFlingPlatformGestureCurve.h" +#include "TraceEvent.h" +#include "WebCompositorImpl.h" +#include "WebCompositorInputHandlerClient.h" +#include "WebInputEvent.h" +#include <wtf/ThreadingPrimitives.h> + +using namespace WebCore; + +namespace WebCore { + +PassOwnPtr<CCInputHandler> CCInputHandler::create(CCInputHandlerClient* inputHandlerClient) +{ + return WebKit::WebCompositorInputHandlerImpl::create(inputHandlerClient); +} + +class PlatformGestureToCCGestureAdapter : public CCGestureCurve, public PlatformGestureCurveTarget { +public: + static PassOwnPtr<CCGestureCurve> create(PassOwnPtr<PlatformGestureCurve> platformCurve) + { + return adoptPtr(new PlatformGestureToCCGestureAdapter(platformCurve)); + } + + virtual const char* debugName() const + { + return m_curve->debugName(); + } + + virtual bool apply(double time, CCGestureCurveTarget* target) + { + ASSERT(target); + m_target = target; + return m_curve->apply(time, this); + } + + virtual void scrollBy(const IntPoint& scrollDelta) + { + ASSERT(m_target); + m_target->scrollBy(scrollDelta); + } + +private: + PlatformGestureToCCGestureAdapter(PassOwnPtr<PlatformGestureCurve> curve) + : m_curve(curve) + , m_target(0) + { + } + + OwnPtr<PlatformGestureCurve> m_curve; + CCGestureCurveTarget* m_target; +}; + +} + +namespace WebKit { + +// These statics may only be accessed from the compositor thread. +int WebCompositorInputHandlerImpl::s_nextAvailableIdentifier = 1; +HashSet<WebCompositorInputHandlerImpl*>* WebCompositorInputHandlerImpl::s_compositors = 0; + +WebCompositorInputHandler* WebCompositorInputHandler::fromIdentifier(int identifier) +{ + return WebCompositorInputHandlerImpl::fromIdentifier(identifier); +} + +PassOwnPtr<WebCompositorInputHandlerImpl> WebCompositorInputHandlerImpl::create(WebCore::CCInputHandlerClient* inputHandlerClient) +{ + return adoptPtr(new WebCompositorInputHandlerImpl(inputHandlerClient)); +} + +WebCompositorInputHandler* WebCompositorInputHandlerImpl::fromIdentifier(int identifier) +{ + ASSERT(WebCompositorImpl::initialized()); + ASSERT(CCProxy::isImplThread()); + + if (!s_compositors) + return 0; + + for (HashSet<WebCompositorInputHandlerImpl*>::iterator it = s_compositors->begin(); it != s_compositors->end(); ++it) { + if ((*it)->identifier() == identifier) + return *it; + } + return 0; +} + +WebCompositorInputHandlerImpl::WebCompositorInputHandlerImpl(CCInputHandlerClient* inputHandlerClient) + : m_client(0) + , m_identifier(s_nextAvailableIdentifier++) + , m_inputHandlerClient(inputHandlerClient) +#ifndef NDEBUG + , m_expectScrollUpdateEnd(false) + , m_expectPinchUpdateEnd(false) +#endif + , m_gestureScrollStarted(false) +{ + ASSERT(CCProxy::isImplThread()); + + if (!s_compositors) + s_compositors = new HashSet<WebCompositorInputHandlerImpl*>; + s_compositors->add(this); +} + +WebCompositorInputHandlerImpl::~WebCompositorInputHandlerImpl() +{ + ASSERT(CCProxy::isImplThread()); + if (m_client) + m_client->willShutdown(); + + ASSERT(s_compositors); + s_compositors->remove(this); + if (!s_compositors->size()) { + delete s_compositors; + s_compositors = 0; + } +} + +void WebCompositorInputHandlerImpl::setClient(WebCompositorInputHandlerClient* client) +{ + ASSERT(CCProxy::isImplThread()); + // It's valid to set a new client if we've never had one or to clear the client, but it's not valid to change from having one client to a different one. + ASSERT(!m_client || !client); + m_client = client; +} + +void WebCompositorInputHandlerImpl::handleInputEvent(const WebInputEvent& event) +{ + ASSERT(CCProxy::isImplThread()); + ASSERT(m_client); + + WebCompositorInputHandlerImpl::EventDisposition disposition = handleInputEventInternal(event); + switch (disposition) { + case DidHandle: + m_client->didHandleInputEvent(); + break; + case DidNotHandle: + m_client->didNotHandleInputEvent(true /* sendToWidget */); + break; + case DropEvent: + m_client->didNotHandleInputEvent(false /* sendToWidget */); + break; + } +} + +WebCompositorInputHandlerImpl::EventDisposition WebCompositorInputHandlerImpl::handleInputEventInternal(const WebInputEvent& event) +{ + if (event.type == WebInputEvent::MouseWheel) { + const WebMouseWheelEvent& wheelEvent = *static_cast<const WebMouseWheelEvent*>(&event); + CCInputHandlerClient::ScrollStatus scrollStatus = m_inputHandlerClient->scrollBegin(IntPoint(wheelEvent.x, wheelEvent.y), CCInputHandlerClient::Wheel); + switch (scrollStatus) { + case CCInputHandlerClient::ScrollStarted: { + TRACE_EVENT_INSTANT2("cc", "WebCompositorInputHandlerImpl::handleInput wheel scroll", "deltaX", -wheelEvent.deltaX, "deltaY", -wheelEvent.deltaY); + m_inputHandlerClient->scrollBy(IntPoint(wheelEvent.x, wheelEvent.y), IntSize(-wheelEvent.deltaX, -wheelEvent.deltaY)); + m_inputHandlerClient->scrollEnd(); + return DidHandle; + } + case CCInputHandlerClient::ScrollIgnored: + // FIXME: This should be DropEvent, but in cases where we fail to properly sync scrollability it's safer to send the + // event to the main thread. Change back to DropEvent once we have synchronization bugs sorted out. + return DidNotHandle; + case CCInputHandlerClient::ScrollOnMainThread: + return DidNotHandle; + } + } else if (event.type == WebInputEvent::GestureScrollBegin) { + ASSERT(!m_gestureScrollStarted); + ASSERT(!m_expectScrollUpdateEnd); +#ifndef NDEBUG + m_expectScrollUpdateEnd = true; +#endif + const WebGestureEvent& gestureEvent = *static_cast<const WebGestureEvent*>(&event); + CCInputHandlerClient::ScrollStatus scrollStatus = m_inputHandlerClient->scrollBegin(IntPoint(gestureEvent.x, gestureEvent.y), CCInputHandlerClient::Gesture); + switch (scrollStatus) { + case CCInputHandlerClient::ScrollStarted: + m_gestureScrollStarted = true; + return DidHandle; + case CCInputHandlerClient::ScrollOnMainThread: + return DidNotHandle; + case CCInputHandlerClient::ScrollIgnored: + return DropEvent; + } + } else if (event.type == WebInputEvent::GestureScrollUpdate) { + ASSERT(m_expectScrollUpdateEnd); + + if (!m_gestureScrollStarted) + return DidNotHandle; + + const WebGestureEvent& gestureEvent = *static_cast<const WebGestureEvent*>(&event); + m_inputHandlerClient->scrollBy(IntPoint(gestureEvent.x, gestureEvent.y), IntSize(-gestureEvent.deltaX, -gestureEvent.deltaY)); + return DidHandle; + } else if (event.type == WebInputEvent::GestureScrollEnd) { + ASSERT(m_expectScrollUpdateEnd); +#ifndef NDEBUG + m_expectScrollUpdateEnd = false; +#endif + if (!m_gestureScrollStarted) + return DidNotHandle; + + m_inputHandlerClient->scrollEnd(); + m_gestureScrollStarted = false; + return DidHandle; + } else if (event.type == WebInputEvent::GesturePinchBegin) { + ASSERT(!m_expectPinchUpdateEnd); +#ifndef NDEBUG + m_expectPinchUpdateEnd = true; +#endif + m_inputHandlerClient->pinchGestureBegin(); + return DidHandle; + } else if (event.type == WebInputEvent::GesturePinchEnd) { + ASSERT(m_expectPinchUpdateEnd); +#ifndef NDEBUG + m_expectPinchUpdateEnd = false; +#endif + m_inputHandlerClient->pinchGestureEnd(); + return DidHandle; + } else if (event.type == WebInputEvent::GesturePinchUpdate) { + ASSERT(m_expectPinchUpdateEnd); + const WebGestureEvent& gestureEvent = *static_cast<const WebGestureEvent*>(&event); + m_inputHandlerClient->pinchGestureUpdate(gestureEvent.deltaX, IntPoint(gestureEvent.x, gestureEvent.y)); + return DidHandle; + } else if (event.type == WebInputEvent::GestureFlingStart) { + const WebGestureEvent& gestureEvent = *static_cast<const WebGestureEvent*>(&event); + return handleGestureFling(gestureEvent); + } else if (event.type == WebInputEvent::GestureFlingCancel) { + if (cancelCurrentFling()) + return DidHandle; + } else if (WebInputEvent::isKeyboardEventType(event.type)) { + cancelCurrentFling(); + } + + return DidNotHandle; +} + +WebCompositorInputHandlerImpl::EventDisposition WebCompositorInputHandlerImpl::handleGestureFling(const WebGestureEvent& gestureEvent) +{ + CCInputHandlerClient::ScrollStatus scrollStatus = m_inputHandlerClient->scrollBegin(IntPoint(gestureEvent.x, gestureEvent.y), CCInputHandlerClient::Gesture); + switch (scrollStatus) { + case CCInputHandlerClient::ScrollStarted: { + TRACE_EVENT_INSTANT0("cc", "WebCompositorInputHandlerImpl::handleGestureFling::started"); + OwnPtr<PlatformGestureCurve> flingCurve = TouchpadFlingPlatformGestureCurve::create(FloatPoint(gestureEvent.deltaX, gestureEvent.deltaY)); + m_wheelFlingAnimation = CCActiveGestureAnimation::create(PlatformGestureToCCGestureAdapter::create(flingCurve.release()), this); + m_wheelFlingParameters.delta = WebFloatPoint(gestureEvent.deltaX, gestureEvent.deltaY); + m_wheelFlingParameters.point = WebPoint(gestureEvent.x, gestureEvent.y); + m_wheelFlingParameters.globalPoint = WebPoint(gestureEvent.globalX, gestureEvent.globalY); + m_wheelFlingParameters.modifiers = gestureEvent.modifiers; + m_inputHandlerClient->scheduleAnimation(); + return DidHandle; + } + case CCInputHandlerClient::ScrollOnMainThread: { + TRACE_EVENT_INSTANT0("cc", "WebCompositorInputHandlerImpl::handleGestureFling::scrollOnMainThread"); + return DidNotHandle; + } + case CCInputHandlerClient::ScrollIgnored: { + TRACE_EVENT_INSTANT0("cc", "WebCompositorInputHandlerImpl::handleGestureFling::ignored"); + // We still pass the curve to the main thread if there's nothing scrollable, in case something + // registers a handler before the curve is over. + return DidNotHandle; + } + } + return DidNotHandle; +} + +int WebCompositorInputHandlerImpl::identifier() const +{ + ASSERT(CCProxy::isImplThread()); + return m_identifier; +} + +void WebCompositorInputHandlerImpl::animate(double monotonicTime) +{ + if (!m_wheelFlingAnimation) + return; + + if (!m_wheelFlingParameters.startTime) + m_wheelFlingParameters.startTime = monotonicTime; + + if (m_wheelFlingAnimation->animate(monotonicTime)) + m_inputHandlerClient->scheduleAnimation(); + else { + TRACE_EVENT_INSTANT0("cc", "WebCompositorInputHandlerImpl::animate::flingOver"); + cancelCurrentFling(); + } +} + +bool WebCompositorInputHandlerImpl::cancelCurrentFling() +{ + bool hadFlingAnimation = m_wheelFlingAnimation; + TRACE_EVENT_INSTANT1("cc", "WebCompositorInputHandlerImpl::cancelCurrentFling", "hadFlingAnimation", hadFlingAnimation); + m_wheelFlingAnimation.clear(); + m_wheelFlingParameters = WebActiveWheelFlingParameters(); + return hadFlingAnimation; +} + +void WebCompositorInputHandlerImpl::scrollBy(const IntPoint& increment) +{ + if (increment == IntPoint::zero()) + return; + + TRACE_EVENT2("cc", "WebCompositorInputHandlerImpl::scrollBy", "x", increment.x(), "y", increment.y()); + WebMouseWheelEvent syntheticWheel; + syntheticWheel.type = WebInputEvent::MouseWheel; + syntheticWheel.deltaX = increment.x(); + syntheticWheel.deltaY = increment.y(); + syntheticWheel.hasPreciseScrollingDeltas = true; + syntheticWheel.x = m_wheelFlingParameters.point.x; + syntheticWheel.y = m_wheelFlingParameters.point.y; + syntheticWheel.globalX = m_wheelFlingParameters.globalPoint.x; + syntheticWheel.globalY = m_wheelFlingParameters.globalPoint.y; + syntheticWheel.modifiers = m_wheelFlingParameters.modifiers; + + WebCompositorInputHandlerImpl::EventDisposition disposition = handleInputEventInternal(syntheticWheel); + switch (disposition) { + case DidHandle: + m_wheelFlingParameters.cumulativeScroll.width += increment.x(); + m_wheelFlingParameters.cumulativeScroll.height += increment.y(); + case DropEvent: + break; + case DidNotHandle: + TRACE_EVENT_INSTANT0("cc", "WebCompositorInputHandlerImpl::scrollBy::AbortFling"); + // If we got a DidNotHandle, that means we need to deliver wheels on the main thread. + // In this case we need to schedule a commit and transfer the fling curve over to the main + // thread and run the rest of the wheels from there. + // This can happen when flinging a page that contains a scrollable subarea that we can't + // scroll on the thread if the fling starts outside the subarea but then is flung "under" the + // pointer. + m_client->transferActiveWheelFlingAnimation(m_wheelFlingParameters); + cancelCurrentFling(); + break; + } +} + +} diff --git a/webkit/compositor/WebCompositorInputHandlerImpl.h b/webkit/compositor/WebCompositorInputHandlerImpl.h new file mode 100644 index 0000000..6f1c3e1 --- /dev/null +++ b/webkit/compositor/WebCompositorInputHandlerImpl.h @@ -0,0 +1,85 @@ +// 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. + +#ifndef WebCompositorInputHandlerImpl_h +#define WebCompositorInputHandlerImpl_h + +#include "CCGestureCurve.h" +#include "CCInputHandler.h" +#include "WebActiveWheelFlingParameters.h" +#include "WebCompositorInputHandler.h" +#include "WebInputEvent.h" +#include <public/WebCompositor.h> +#include <wtf/HashSet.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> + +namespace WTF { +class Mutex; +} + +namespace WebCore { +class IntPoint; +class CCGestureCurveTarget; +class CCInputHandlerClient; +class CCThread; +} + +namespace WebKit { + +class WebCompositorInputHandlerClient; + +class WebCompositorInputHandlerImpl : public WebCompositorInputHandler, public WebCore::CCInputHandler, public WebCore::CCGestureCurveTarget { + WTF_MAKE_NONCOPYABLE(WebCompositorInputHandlerImpl); +public: + static PassOwnPtr<WebCompositorInputHandlerImpl> create(WebCore::CCInputHandlerClient*); + static WebCompositorInputHandler* fromIdentifier(int identifier); + + virtual ~WebCompositorInputHandlerImpl(); + + // WebCompositorInputHandler implementation. + virtual void setClient(WebCompositorInputHandlerClient*); + virtual void handleInputEvent(const WebInputEvent&); + + // WebCore::CCInputHandler implementation. + virtual int identifier() const; + virtual void animate(double monotonicTime); + + // WebCore::CCGestureCurveTarget implementation. + virtual void scrollBy(const WebCore::IntPoint&); + +private: + explicit WebCompositorInputHandlerImpl(WebCore::CCInputHandlerClient*); + + enum EventDisposition { DidHandle, DidNotHandle, DropEvent }; + // This function processes the input event and determines the disposition, but does not make + // any calls out to the WebCompositorInputHandlerClient. Some input types defer to helpers. + EventDisposition handleInputEventInternal(const WebInputEvent&); + + EventDisposition handleGestureFling(const WebGestureEvent&); + + // Returns true if we actually had an active fling to cancel. + bool cancelCurrentFling(); + + OwnPtr<WebCore::CCActiveGestureAnimation> m_wheelFlingAnimation; + // Parameters for the active fling animation, stored in case we need to transfer it out later. + WebActiveWheelFlingParameters m_wheelFlingParameters; + + WebCompositorInputHandlerClient* m_client; + int m_identifier; + WebCore::CCInputHandlerClient* m_inputHandlerClient; + +#ifndef NDEBUG + bool m_expectScrollUpdateEnd; + bool m_expectPinchUpdateEnd; +#endif + bool m_gestureScrollStarted; + + static int s_nextAvailableIdentifier; + static HashSet<WebCompositorInputHandlerImpl*>* s_compositors; +}; + +} + +#endif // WebCompositorImpl_h diff --git a/webkit/compositor/WebContentLayerImpl.cpp b/webkit/compositor/WebContentLayerImpl.cpp new file mode 100644 index 0000000..c1f3a1a --- /dev/null +++ b/webkit/compositor/WebContentLayerImpl.cpp @@ -0,0 +1,71 @@ +// 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 "config.h" +#include "WebContentLayerImpl.h" + +#include "SkMatrix44.h" +#include <public/WebContentLayerClient.h> +#include <public/WebFloatPoint.h> +#include <public/WebFloatRect.h> +#include <public/WebRect.h> +#include <public/WebSize.h> + +using namespace WebCore; + +namespace WebKit { + +WebContentLayer* WebContentLayer::create(WebContentLayerClient* client) +{ + return new WebContentLayerImpl(client); +} + +WebContentLayerImpl::WebContentLayerImpl(WebContentLayerClient* client) + : m_webLayerImpl(adoptPtr(new WebLayerImpl(ContentLayerChromium::create(this)))) + , m_client(client) +{ + m_webLayerImpl->layer()->setIsDrawable(true); +} + +WebContentLayerImpl::~WebContentLayerImpl() +{ + static_cast<ContentLayerChromium*>(m_webLayerImpl->layer())->clearDelegate(); +} + +WebLayer* WebContentLayerImpl::layer() +{ + return m_webLayerImpl.get(); +} + +void WebContentLayerImpl::setDoubleSided(bool doubleSided) +{ + m_webLayerImpl->layer()->setDoubleSided(doubleSided); +} + +void WebContentLayerImpl::setContentsScale(float scale) +{ + m_webLayerImpl->layer()->setContentsScale(scale); +} + +void WebContentLayerImpl::setUseLCDText(bool enable) +{ + m_webLayerImpl->layer()->setUseLCDText(enable); +} + +void WebContentLayerImpl::setDrawCheckerboardForMissingTiles(bool enable) +{ + m_webLayerImpl->layer()->setDrawCheckerboardForMissingTiles(enable); +} + + +void WebContentLayerImpl::paintContents(SkCanvas* canvas, const IntRect& clip, FloatRect& opaque) +{ + if (!m_client) + return; + WebFloatRect webOpaque; + m_client->paintContents(canvas, WebRect(clip), webOpaque); + opaque = webOpaque; +} + +} // namespace WebKit diff --git a/webkit/compositor/WebContentLayerImpl.h b/webkit/compositor/WebContentLayerImpl.h new file mode 100644 index 0000000..ee75b16 --- /dev/null +++ b/webkit/compositor/WebContentLayerImpl.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef WebContentLayerImpl_h +#define WebContentLayerImpl_h + +#include "ContentLayerChromium.h" +#include "WebLayerImpl.h" +#include <public/WebContentLayer.h> +#include <wtf/PassRefPtr.h> + +namespace WebKit { +class WebContentLayerClient; + +class WebContentLayerImpl : public WebContentLayer, + public WebCore::ContentLayerDelegate { +public: + explicit WebContentLayerImpl(WebContentLayerClient*); + + // WebContentLayer implementation. + virtual WebLayer* layer() OVERRIDE; + virtual void setDoubleSided(bool) OVERRIDE; + virtual void setContentsScale(float) OVERRIDE; + virtual void setUseLCDText(bool) OVERRIDE; + virtual void setDrawCheckerboardForMissingTiles(bool) OVERRIDE; + +protected: + virtual ~WebContentLayerImpl(); + + // ContentLayerDelegate implementation. + virtual void paintContents(SkCanvas*, const WebCore::IntRect& clip, WebCore::FloatRect& opaque) OVERRIDE; + + OwnPtr<WebLayerImpl> m_webLayerImpl; + WebContentLayerClient* m_client; + bool m_drawsContent; +}; + +} // namespace WebKit + +#endif // WebContentLayerImpl_h diff --git a/webkit/compositor/WebExternalTextureLayerImpl.cpp b/webkit/compositor/WebExternalTextureLayerImpl.cpp new file mode 100644 index 0000000..0537fc2 --- /dev/null +++ b/webkit/compositor/WebExternalTextureLayerImpl.cpp @@ -0,0 +1,111 @@ +// 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 "config.h" +#include "WebExternalTextureLayerImpl.h" + +#include "CCTextureUpdateQueue.h" +#include "TextureLayerChromium.h" +#include "WebLayerImpl.h" +#include <public/WebExternalTextureLayerClient.h> +#include <public/WebFloatRect.h> +#include <public/WebSize.h> + +using namespace WebCore; + +namespace WebKit { + +WebExternalTextureLayer* WebExternalTextureLayer::create(WebExternalTextureLayerClient* client) +{ + return new WebExternalTextureLayerImpl(client); +} + +WebExternalTextureLayerImpl::WebExternalTextureLayerImpl(WebExternalTextureLayerClient* client) + : m_client(client) +{ + RefPtr<TextureLayerChromium> layer; + if (m_client) + layer = TextureLayerChromium::create(this); + else + layer = TextureLayerChromium::create(0); + layer->setIsDrawable(true); + m_layer = adoptPtr(new WebLayerImpl(layer.release())); +} + +WebExternalTextureLayerImpl::~WebExternalTextureLayerImpl() +{ + static_cast<TextureLayerChromium*>(m_layer->layer())->clearClient(); +} + +WebLayer* WebExternalTextureLayerImpl::layer() +{ + return m_layer.get(); +} + +void WebExternalTextureLayerImpl::setTextureId(unsigned id) +{ + static_cast<TextureLayerChromium*>(m_layer->layer())->setTextureId(id); +} + +void WebExternalTextureLayerImpl::setFlipped(bool flipped) +{ + static_cast<TextureLayerChromium*>(m_layer->layer())->setFlipped(flipped); +} + +void WebExternalTextureLayerImpl::setUVRect(const WebFloatRect& rect) +{ + static_cast<TextureLayerChromium*>(m_layer->layer())->setUVRect(rect); +} + +void WebExternalTextureLayerImpl::setOpaque(bool opaque) +{ + static_cast<TextureLayerChromium*>(m_layer->layer())->setOpaque(opaque); +} + +void WebExternalTextureLayerImpl::setPremultipliedAlpha(bool premultipliedAlpha) +{ + static_cast<TextureLayerChromium*>(m_layer->layer())->setPremultipliedAlpha(premultipliedAlpha); +} + +void WebExternalTextureLayerImpl::willModifyTexture() +{ + static_cast<TextureLayerChromium*>(m_layer->layer())->willModifyTexture(); +} + +void WebExternalTextureLayerImpl::setRateLimitContext(bool rateLimit) +{ + static_cast<TextureLayerChromium*>(m_layer->layer())->setRateLimitContext(rateLimit); +} + +class WebTextureUpdaterImpl : public WebTextureUpdater { +public: + explicit WebTextureUpdaterImpl(CCTextureUpdateQueue& queue) + : m_queue(queue) + { + } + + virtual void appendCopy(unsigned sourceTexture, unsigned destinationTexture, WebSize size) OVERRIDE + { + TextureCopier::Parameters copy = { sourceTexture, destinationTexture, size }; + m_queue.appendCopy(copy); + } + +private: + CCTextureUpdateQueue& m_queue; +}; + +unsigned WebExternalTextureLayerImpl::prepareTexture(CCTextureUpdateQueue& queue) +{ + ASSERT(m_client); + WebTextureUpdaterImpl updaterImpl(queue); + return m_client->prepareTexture(updaterImpl); +} + +WebGraphicsContext3D* WebExternalTextureLayerImpl::context() +{ + ASSERT(m_client); + return m_client->context(); +} + +} // namespace WebKit diff --git a/webkit/compositor/WebExternalTextureLayerImpl.h b/webkit/compositor/WebExternalTextureLayerImpl.h new file mode 100644 index 0000000..848ef0c --- /dev/null +++ b/webkit/compositor/WebExternalTextureLayerImpl.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef WebExternalTextureLayerImpl_h +#define WebExternalTextureLayerImpl_h + +#include "TextureLayerChromium.h" +#include <public/WebExternalTextureLayer.h> + +namespace WebKit { + +class WebLayerImpl; + +class WebExternalTextureLayerImpl : public WebExternalTextureLayer, + public WebCore::TextureLayerChromiumClient { +public: + explicit WebExternalTextureLayerImpl(WebExternalTextureLayerClient*); + virtual ~WebExternalTextureLayerImpl(); + + // WebExternalTextureLayer implementation. + virtual WebLayer* layer() OVERRIDE; + virtual void setTextureId(unsigned) OVERRIDE; + virtual void setFlipped(bool) OVERRIDE; + virtual void setUVRect(const WebFloatRect&) OVERRIDE; + virtual void setOpaque(bool) OVERRIDE; + virtual void setPremultipliedAlpha(bool) OVERRIDE; + virtual void willModifyTexture() OVERRIDE; + virtual void setRateLimitContext(bool) OVERRIDE; + + // TextureLayerChromiumClient implementation. + virtual unsigned prepareTexture(WebCore::CCTextureUpdateQueue&) OVERRIDE; + virtual WebGraphicsContext3D* context() OVERRIDE; + +private: + WebExternalTextureLayerClient* m_client; + OwnPtr<WebLayerImpl> m_layer; +}; + +} + +#endif // WebExternalTextureLayerImpl_h + diff --git a/webkit/compositor/WebFloatAnimationCurveImpl.cpp b/webkit/compositor/WebFloatAnimationCurveImpl.cpp new file mode 100644 index 0000000..7ea8a89 --- /dev/null +++ b/webkit/compositor/WebFloatAnimationCurveImpl.cpp @@ -0,0 +1,62 @@ +// Copyright 2012 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 "WebFloatAnimationCurveImpl.h" + +#include "CCAnimationCurve.h" +#include "CCKeyframedAnimationCurve.h" +#include "CCTimingFunction.h" +#include "WebAnimationCurveCommon.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebKit { + +WebFloatAnimationCurve* WebFloatAnimationCurve::create() +{ + return new WebFloatAnimationCurveImpl(WebCore::CCKeyframedFloatAnimationCurve::create()); +} + +WebFloatAnimationCurveImpl::WebFloatAnimationCurveImpl(PassOwnPtr<WebCore::CCKeyframedFloatAnimationCurve> curve) + : m_curve(curve) +{ +} + +WebFloatAnimationCurveImpl::~WebFloatAnimationCurveImpl() +{ +} + +WebAnimationCurve::AnimationCurveType WebFloatAnimationCurveImpl::type() const +{ + return WebAnimationCurve::AnimationCurveTypeFloat; +} + +void WebFloatAnimationCurveImpl::add(const WebFloatKeyframe& keyframe) +{ + add(keyframe, TimingFunctionTypeEase); +} + +void WebFloatAnimationCurveImpl::add(const WebFloatKeyframe& keyframe, TimingFunctionType type) +{ + m_curve->addKeyframe(WebCore::CCFloatKeyframe::create(keyframe.time, keyframe.value, createTimingFunction(type))); +} + +void WebFloatAnimationCurveImpl::add(const WebFloatKeyframe& keyframe, double x1, double y1, double x2, double y2) +{ + m_curve->addKeyframe(WebCore::CCFloatKeyframe::create(keyframe.time, keyframe.value, WebCore::CCCubicBezierTimingFunction::create(x1, y1, x2, y2))); +} + +float WebFloatAnimationCurveImpl::getValue(double time) const +{ + return m_curve->getValue(time); +} + +PassOwnPtr<WebCore::CCAnimationCurve> WebFloatAnimationCurveImpl::cloneToCCAnimationCurve() const +{ + return m_curve->clone(); +} + +} // namespace WebKit diff --git a/webkit/compositor/WebFloatAnimationCurveImpl.h b/webkit/compositor/WebFloatAnimationCurveImpl.h new file mode 100644 index 0000000..6a05608 --- /dev/null +++ b/webkit/compositor/WebFloatAnimationCurveImpl.h @@ -0,0 +1,42 @@ +// Copyright 2012 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. + +#ifndef WebFloatAnimationCurveImpl_h +#define WebFloatAnimationCurveImpl_h + +#include <public/WebFloatAnimationCurve.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { +class CCAnimationCurve; +class CCKeyframedFloatAnimationCurve; +} + +namespace WebKit { + +class WebFloatAnimationCurveImpl : public WebFloatAnimationCurve { +public: + explicit WebFloatAnimationCurveImpl(PassOwnPtr<WebCore::CCKeyframedFloatAnimationCurve>); + virtual ~WebFloatAnimationCurveImpl(); + + // WebAnimationCurve implementation. + virtual AnimationCurveType type() const OVERRIDE; + + // WebFloatAnimationCurve implementation. + virtual void add(const WebFloatKeyframe&) OVERRIDE; + virtual void add(const WebFloatKeyframe&, TimingFunctionType) OVERRIDE; + virtual void add(const WebFloatKeyframe&, double x1, double y1, double x2, double y2) OVERRIDE; + + virtual float getValue(double time) const OVERRIDE; + + PassOwnPtr<WebCore::CCAnimationCurve> cloneToCCAnimationCurve() const; + +private: + OwnPtr<WebCore::CCKeyframedFloatAnimationCurve> m_curve; +}; + +} + +#endif // WebFloatAnimationCurveImpl_h diff --git a/webkit/compositor/WebIOSurfaceLayerImpl.cpp b/webkit/compositor/WebIOSurfaceLayerImpl.cpp new file mode 100644 index 0000000..1ce29ef --- /dev/null +++ b/webkit/compositor/WebIOSurfaceLayerImpl.cpp @@ -0,0 +1,41 @@ +// Copyright 2012 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 "WebIOSurfaceLayerImpl.h" + +#include "IOSurfaceLayerChromium.h" +#include "WebLayerImpl.h" + +using WebCore::IOSurfaceLayerChromium; + +namespace WebKit { + +WebIOSurfaceLayer* WebIOSurfaceLayer::create() +{ + RefPtr<IOSurfaceLayerChromium> layer = IOSurfaceLayerChromium::create(); + layer->setIsDrawable(true); + return new WebIOSurfaceLayerImpl(layer.release()); +} + +WebIOSurfaceLayerImpl::WebIOSurfaceLayerImpl(PassRefPtr<IOSurfaceLayerChromium> layer) + : m_layer(adoptPtr(new WebLayerImpl(layer))) +{ +} + +WebIOSurfaceLayerImpl::~WebIOSurfaceLayerImpl() +{ +} + +void WebIOSurfaceLayerImpl::setIOSurfaceProperties(unsigned ioSurfaceId, WebSize size) +{ + static_cast<IOSurfaceLayerChromium*>(m_layer->layer())->setIOSurfaceProperties(ioSurfaceId, size); +} + +WebLayer* WebIOSurfaceLayerImpl::layer() +{ + return m_layer.get(); +} + +} // namespace WebKit diff --git a/webkit/compositor/WebIOSurfaceLayerImpl.h b/webkit/compositor/WebIOSurfaceLayerImpl.h new file mode 100644 index 0000000..c7dc3d7 --- /dev/null +++ b/webkit/compositor/WebIOSurfaceLayerImpl.h @@ -0,0 +1,33 @@ +// Copyright 2012 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. + +#ifndef WebIOSurfaceLayerImpl_h +#define WebIOSurfaceLayerImpl_h + +#include <public/WebIOSurfaceLayer.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { +class IOSurfaceLayerChromium; +} + +namespace WebKit { + +class WebIOSurfaceLayerImpl : public WebIOSurfaceLayer { +public: + explicit WebIOSurfaceLayerImpl(PassRefPtr<WebCore::IOSurfaceLayerChromium>); + virtual ~WebIOSurfaceLayerImpl(); + + // WebIOSurfaceLayer implementation. + virtual WebLayer* layer() OVERRIDE; + virtual void setIOSurfaceProperties(unsigned ioSurfaceId, WebSize) OVERRIDE; + +private: + OwnPtr<WebLayerImpl> m_layer; +}; + +} + +#endif // WebIOSurfaceLayerImpl_h + diff --git a/webkit/compositor/WebImageLayerImpl.cpp b/webkit/compositor/WebImageLayerImpl.cpp new file mode 100644 index 0000000..e77b972 --- /dev/null +++ b/webkit/compositor/WebImageLayerImpl.cpp @@ -0,0 +1,39 @@ +// Copyright 2012 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 "WebImageLayerImpl.h" + +#include "ImageLayerChromium.h" +#include "WebLayerImpl.h" + +using WebCore::ImageLayerChromium; + +namespace WebKit { + +WebImageLayer* WebImageLayer::create() +{ + return new WebImageLayerImpl(WebCore::ImageLayerChromium::create()); +} + +WebImageLayerImpl::WebImageLayerImpl(PassRefPtr<WebCore::ImageLayerChromium> layer) + : m_layer(adoptPtr(new WebLayerImpl(layer))) +{ +} + +WebImageLayerImpl::~WebImageLayerImpl() +{ +} + +WebLayer* WebImageLayerImpl::layer() +{ + return m_layer.get(); +} + +void WebImageLayerImpl::setBitmap(SkBitmap bitmap) +{ + static_cast<ImageLayerChromium*>(m_layer->layer())->setBitmap(bitmap); +} + +} // namespace WebKit diff --git a/webkit/compositor/WebImageLayerImpl.h b/webkit/compositor/WebImageLayerImpl.h new file mode 100644 index 0000000..3c16b0e --- /dev/null +++ b/webkit/compositor/WebImageLayerImpl.h @@ -0,0 +1,33 @@ +// Copyright 2012 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. + +#ifndef WebImageLayerImpl_h +#define WebImageLayerImpl_h + +#include <public/WebImageLayer.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { +class ImageLayerChromium; +} + +namespace WebKit { +class WebLayerImpl; + +class WebImageLayerImpl : public WebImageLayer { +public: + explicit WebImageLayerImpl(PassRefPtr<WebCore::ImageLayerChromium>); + virtual ~WebImageLayerImpl(); + + // WebImageLayer implementation. + WebLayer* layer() OVERRIDE; + virtual void setBitmap(SkBitmap) OVERRIDE; + +private: + OwnPtr<WebLayerImpl> m_layer; +}; + +} + +#endif // WebImageLayerImpl_h diff --git a/webkit/compositor/WebLayerImpl.cpp b/webkit/compositor/WebLayerImpl.cpp new file mode 100644 index 0000000..0c913f9 --- /dev/null +++ b/webkit/compositor/WebLayerImpl.cpp @@ -0,0 +1,372 @@ +// 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 "config.h" +#include "WebLayerImpl.h" + +#include "CCActiveAnimation.h" +#include "LayerChromium.h" +#include "SkMatrix44.h" +#include "WebAnimationImpl.h" +#include <public/WebFloatPoint.h> +#include <public/WebFloatRect.h> +#include <public/WebSize.h> +#include <public/WebTransformationMatrix.h> + +using WebCore::CCActiveAnimation; +using WebCore::LayerChromium; + +namespace WebKit { + +namespace { + +WebTransformationMatrix transformationMatrixFromSkMatrix44(const SkMatrix44& matrix) +{ + double data[16]; + matrix.asColMajord(data); + return WebTransformationMatrix(data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15]); +} + +SkMatrix44 skMatrix44FromTransformationMatrix(const WebTransformationMatrix& matrix) +{ + SkMatrix44 skMatrix; + skMatrix.set(0, 0, SkDoubleToMScalar(matrix.m11())); + skMatrix.set(1, 0, SkDoubleToMScalar(matrix.m12())); + skMatrix.set(2, 0, SkDoubleToMScalar(matrix.m13())); + skMatrix.set(3, 0, SkDoubleToMScalar(matrix.m14())); + skMatrix.set(0, 1, SkDoubleToMScalar(matrix.m21())); + skMatrix.set(1, 1, SkDoubleToMScalar(matrix.m22())); + skMatrix.set(2, 1, SkDoubleToMScalar(matrix.m23())); + skMatrix.set(3, 1, SkDoubleToMScalar(matrix.m24())); + skMatrix.set(0, 2, SkDoubleToMScalar(matrix.m31())); + skMatrix.set(1, 2, SkDoubleToMScalar(matrix.m32())); + skMatrix.set(2, 2, SkDoubleToMScalar(matrix.m33())); + skMatrix.set(3, 2, SkDoubleToMScalar(matrix.m34())); + skMatrix.set(0, 3, SkDoubleToMScalar(matrix.m41())); + skMatrix.set(1, 3, SkDoubleToMScalar(matrix.m42())); + skMatrix.set(2, 3, SkDoubleToMScalar(matrix.m43())); + skMatrix.set(3, 3, SkDoubleToMScalar(matrix.m44())); + return skMatrix; +} + +} // anonymous namespace + + +WebLayer* WebLayer::create() +{ + return new WebLayerImpl(LayerChromium::create()); +} + +WebLayerImpl::WebLayerImpl(PassRefPtr<LayerChromium> layer) + : m_layer(layer) +{ +} + +WebLayerImpl::~WebLayerImpl() +{ + m_layer->clearRenderSurface(); + m_layer->setLayerAnimationDelegate(0); +} + +int WebLayerImpl::id() const +{ + return m_layer->id(); +} + +void WebLayerImpl::invalidateRect(const WebFloatRect& rect) +{ + m_layer->setNeedsDisplayRect(rect); +} + +void WebLayerImpl::invalidate() +{ + m_layer->setNeedsDisplay(); +} + +void WebLayerImpl::addChild(WebLayer* child) +{ + m_layer->addChild(static_cast<WebLayerImpl*>(child)->layer()); +} + +void WebLayerImpl::insertChild(WebLayer* child, size_t index) +{ + m_layer->insertChild(static_cast<WebLayerImpl*>(child)->layer(), index); +} + +void WebLayerImpl::replaceChild(WebLayer* reference, WebLayer* newLayer) +{ + m_layer->replaceChild(static_cast<WebLayerImpl*>(reference)->layer(), static_cast<WebLayerImpl*>(newLayer)->layer()); +} + +void WebLayerImpl::removeFromParent() +{ + m_layer->removeFromParent(); +} + +void WebLayerImpl::removeAllChildren() +{ + m_layer->removeAllChildren(); +} + +void WebLayerImpl::setAnchorPoint(const WebFloatPoint& anchorPoint) +{ + m_layer->setAnchorPoint(anchorPoint); +} + +WebFloatPoint WebLayerImpl::anchorPoint() const +{ + return WebFloatPoint(m_layer->anchorPoint()); +} + +void WebLayerImpl::setAnchorPointZ(float anchorPointZ) +{ + m_layer->setAnchorPointZ(anchorPointZ); +} + +float WebLayerImpl::anchorPointZ() const +{ + return m_layer->anchorPointZ(); +} + +void WebLayerImpl::setBounds(const WebSize& size) +{ + m_layer->setBounds(size); +} + +WebSize WebLayerImpl::bounds() const +{ + return WebSize(m_layer->bounds()); +} + +void WebLayerImpl::setMasksToBounds(bool masksToBounds) +{ + m_layer->setMasksToBounds(masksToBounds); +} + +bool WebLayerImpl::masksToBounds() const +{ + return m_layer->masksToBounds(); +} + +void WebLayerImpl::setMaskLayer(WebLayer* maskLayer) +{ + m_layer->setMaskLayer(maskLayer ? static_cast<WebLayerImpl*>(maskLayer)->layer() : 0); +} + +void WebLayerImpl::setReplicaLayer(WebLayer* replicaLayer) +{ + m_layer->setReplicaLayer(replicaLayer ? static_cast<WebLayerImpl*>(replicaLayer)->layer() : 0); +} + +void WebLayerImpl::setOpacity(float opacity) +{ + m_layer->setOpacity(opacity); +} + +float WebLayerImpl::opacity() const +{ + return m_layer->opacity(); +} + +void WebLayerImpl::setOpaque(bool opaque) +{ + m_layer->setOpaque(opaque); +} + +bool WebLayerImpl::opaque() const +{ + return m_layer->opaque(); +} + +void WebLayerImpl::setPosition(const WebFloatPoint& position) +{ + m_layer->setPosition(position); +} + +WebFloatPoint WebLayerImpl::position() const +{ + return WebFloatPoint(m_layer->position()); +} + +void WebLayerImpl::setSublayerTransform(const SkMatrix44& matrix) +{ + m_layer->setSublayerTransform(transformationMatrixFromSkMatrix44(matrix)); +} + +void WebLayerImpl::setSublayerTransform(const WebTransformationMatrix& matrix) +{ + m_layer->setSublayerTransform(matrix); +} + +SkMatrix44 WebLayerImpl::sublayerTransform() const +{ + return skMatrix44FromTransformationMatrix(m_layer->sublayerTransform()); +} + +void WebLayerImpl::setTransform(const SkMatrix44& matrix) +{ + m_layer->setTransform(transformationMatrixFromSkMatrix44(matrix)); +} + +void WebLayerImpl::setTransform(const WebTransformationMatrix& matrix) +{ + m_layer->setTransform(matrix); +} + +SkMatrix44 WebLayerImpl::transform() const +{ + return skMatrix44FromTransformationMatrix(m_layer->transform()); +} + +void WebLayerImpl::setDrawsContent(bool drawsContent) +{ + m_layer->setIsDrawable(drawsContent); +} + +bool WebLayerImpl::drawsContent() const +{ + return m_layer->drawsContent(); +} + +void WebLayerImpl::setPreserves3D(bool preserve3D) +{ + m_layer->setPreserves3D(preserve3D); +} + +void WebLayerImpl::setUseParentBackfaceVisibility(bool useParentBackfaceVisibility) +{ + m_layer->setUseParentBackfaceVisibility(useParentBackfaceVisibility); +} + +void WebLayerImpl::setBackgroundColor(WebColor color) +{ + m_layer->setBackgroundColor(color); +} + +void WebLayerImpl::setFilters(const WebFilterOperations& filters) +{ + m_layer->setFilters(filters); +} + +void WebLayerImpl::setBackgroundFilters(const WebFilterOperations& filters) +{ + m_layer->setBackgroundFilters(filters); +} + +void WebLayerImpl::setDebugBorderColor(const WebColor& color) +{ + m_layer->setDebugBorderColor(color); +} + +void WebLayerImpl::setDebugBorderWidth(float width) +{ + m_layer->setDebugBorderWidth(width); +} + +void WebLayerImpl::setDebugName(WebString name) +{ + m_layer->setDebugName(name); +} + +void WebLayerImpl::setAnimationDelegate(WebAnimationDelegate* delegate) +{ + m_layer->setLayerAnimationDelegate(delegate); +} + +bool WebLayerImpl::addAnimation(WebAnimation* animation) +{ + return m_layer->addAnimation(static_cast<WebAnimationImpl*>(animation)->cloneToCCAnimation()); +} + +void WebLayerImpl::removeAnimation(int animationId) +{ + m_layer->removeAnimation(animationId); +} + +void WebLayerImpl::removeAnimation(int animationId, WebAnimation::TargetProperty targetProperty) +{ + m_layer->layerAnimationController()->removeAnimation(animationId, static_cast<CCActiveAnimation::TargetProperty>(targetProperty)); +} + +void WebLayerImpl::pauseAnimation(int animationId, double timeOffset) +{ + m_layer->pauseAnimation(animationId, timeOffset); +} + +void WebLayerImpl::suspendAnimations(double monotonicTime) +{ + m_layer->suspendAnimations(monotonicTime); +} + +void WebLayerImpl::resumeAnimations(double monotonicTime) +{ + m_layer->resumeAnimations(monotonicTime); +} + +bool WebLayerImpl::hasActiveAnimation() +{ + return m_layer->hasActiveAnimation(); +} + +void WebLayerImpl::transferAnimationsTo(WebLayer* other) +{ + ASSERT(other); + static_cast<WebLayerImpl*>(other)->m_layer->setLayerAnimationController(m_layer->releaseLayerAnimationController()); +} + +void WebLayerImpl::setForceRenderSurface(bool forceRenderSurface) +{ + m_layer->setForceRenderSurface(forceRenderSurface); +} + +void WebLayerImpl::setScrollPosition(WebPoint position) +{ + m_layer->setScrollPosition(position); +} + +void WebLayerImpl::setScrollable(bool scrollable) +{ + m_layer->setScrollable(scrollable); +} + +void WebLayerImpl::setHaveWheelEventHandlers(bool haveWheelEventHandlers) +{ + m_layer->setHaveWheelEventHandlers(haveWheelEventHandlers); +} + +void WebLayerImpl::setShouldScrollOnMainThread(bool shouldScrollOnMainThread) +{ + m_layer->setShouldScrollOnMainThread(shouldScrollOnMainThread); +} + +void WebLayerImpl::setNonFastScrollableRegion(const WebVector<WebRect>& rects) +{ + WebCore::Region region; + for (size_t i = 0; i < rects.size(); ++i) { + WebCore::IntRect rect = rects[i]; + region.unite(rect); + } + m_layer->setNonFastScrollableRegion(region); + +} + +void WebLayerImpl::setIsContainerForFixedPositionLayers(bool enable) +{ + m_layer->setIsContainerForFixedPositionLayers(enable); +} + +void WebLayerImpl::setFixedToContainerLayer(bool enable) +{ + m_layer->setFixedToContainerLayer(enable); +} + +LayerChromium* WebLayerImpl::layer() const +{ + return m_layer.get(); +} + +} // namespace WebKit diff --git a/webkit/compositor/WebLayerImpl.h b/webkit/compositor/WebLayerImpl.h new file mode 100644 index 0000000..b499a30 --- /dev/null +++ b/webkit/compositor/WebLayerImpl.h @@ -0,0 +1,89 @@ +// 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. + +#ifndef WebLayerImpl_h +#define WebLayerImpl_h + +#include <public/WebLayer.h> +#include <wtf/RefPtr.h> + +namespace WebCore { +class LayerChromium; +} + +namespace WebKit { + +class WebLayerImpl : public WebLayer { +public: + explicit WebLayerImpl(PassRefPtr<WebCore::LayerChromium>); + virtual ~WebLayerImpl(); + + // WebLayer implementation. + virtual int id() const OVERRIDE; + virtual void invalidateRect(const WebFloatRect&) OVERRIDE; + virtual void invalidate() OVERRIDE; + virtual void addChild(WebLayer*) OVERRIDE; + virtual void insertChild(WebLayer*, size_t index) OVERRIDE; + virtual void replaceChild(WebLayer* reference, WebLayer* newLayer) OVERRIDE; + virtual void removeFromParent() OVERRIDE; + virtual void removeAllChildren() OVERRIDE; + virtual void setAnchorPoint(const WebFloatPoint&) OVERRIDE; + virtual WebFloatPoint anchorPoint() const OVERRIDE; + virtual void setAnchorPointZ(float) OVERRIDE; + virtual float anchorPointZ() const OVERRIDE; + virtual void setBounds(const WebSize&) OVERRIDE; + virtual WebSize bounds() const OVERRIDE; + virtual void setMasksToBounds(bool) OVERRIDE; + virtual bool masksToBounds() const OVERRIDE; + virtual void setMaskLayer(WebLayer*) OVERRIDE; + virtual void setReplicaLayer(WebLayer*) OVERRIDE; + virtual void setOpacity(float) OVERRIDE; + virtual float opacity() const OVERRIDE; + virtual void setOpaque(bool) OVERRIDE; + virtual bool opaque() const OVERRIDE; + virtual void setPosition(const WebFloatPoint&) OVERRIDE; + virtual WebFloatPoint position() const OVERRIDE; + virtual void setSublayerTransform(const SkMatrix44&) OVERRIDE; + virtual void setSublayerTransform(const WebTransformationMatrix&) OVERRIDE; + virtual SkMatrix44 sublayerTransform() const OVERRIDE; + virtual void setTransform(const SkMatrix44&) OVERRIDE; + virtual void setTransform(const WebTransformationMatrix&) OVERRIDE; + virtual SkMatrix44 transform() const OVERRIDE; + virtual void setDrawsContent(bool) OVERRIDE; + virtual bool drawsContent() const OVERRIDE; + virtual void setPreserves3D(bool) OVERRIDE; + virtual void setUseParentBackfaceVisibility(bool) OVERRIDE; + virtual void setBackgroundColor(WebColor) OVERRIDE; + virtual void setFilters(const WebFilterOperations&) OVERRIDE; + virtual void setBackgroundFilters(const WebFilterOperations&) OVERRIDE; + virtual void setDebugBorderColor(const WebColor&) OVERRIDE; + virtual void setDebugBorderWidth(float) OVERRIDE; + virtual void setDebugName(WebString) OVERRIDE; + virtual void setAnimationDelegate(WebAnimationDelegate*) OVERRIDE; + virtual bool addAnimation(WebAnimation*) OVERRIDE; + virtual void removeAnimation(int animationId) OVERRIDE; + virtual void removeAnimation(int animationId, WebAnimation::TargetProperty) OVERRIDE; + virtual void pauseAnimation(int animationId, double timeOffset) OVERRIDE; + virtual void suspendAnimations(double monotonicTime) OVERRIDE; + virtual void resumeAnimations(double monotonicTime) OVERRIDE; + virtual bool hasActiveAnimation() OVERRIDE; + virtual void transferAnimationsTo(WebLayer*) OVERRIDE; + virtual void setForceRenderSurface(bool) OVERRIDE; + virtual void setScrollPosition(WebPoint) OVERRIDE; + virtual void setScrollable(bool) OVERRIDE; + virtual void setHaveWheelEventHandlers(bool) OVERRIDE; + virtual void setShouldScrollOnMainThread(bool) OVERRIDE; + virtual void setNonFastScrollableRegion(const WebVector<WebRect>&) OVERRIDE; + virtual void setIsContainerForFixedPositionLayers(bool) OVERRIDE; + virtual void setFixedToContainerLayer(bool) OVERRIDE; + + WebCore::LayerChromium* layer() const; + +protected: + RefPtr<WebCore::LayerChromium> m_layer; +}; + +} // namespace WebKit + +#endif // WebLayerImpl_h diff --git a/webkit/compositor/WebLayerTreeViewImpl.cpp b/webkit/compositor/WebLayerTreeViewImpl.cpp new file mode 100644 index 0000000..7bfcff1 --- /dev/null +++ b/webkit/compositor/WebLayerTreeViewImpl.cpp @@ -0,0 +1,255 @@ +// 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 "config.h" +#include "WebLayerTreeViewImpl.h" + +#include "CCFontAtlas.h" +#include "CCLayerTreeHost.h" +#include "LayerChromium.h" +#include "WebLayerImpl.h" +#include <public/WebGraphicsContext3D.h> +#include <public/WebLayer.h> +#include <public/WebLayerTreeView.h> +#include <public/WebLayerTreeViewClient.h> +#include <public/WebRenderingStats.h> +#include <public/WebSize.h> + +using namespace WebCore; + +namespace WebKit { + +WebLayerTreeView* WebLayerTreeView::create(WebLayerTreeViewClient* client, const WebLayer& root, const WebLayerTreeView::Settings& settings) +{ + OwnPtr<WebLayerTreeViewImpl> layerTreeViewImpl = adoptPtr(new WebLayerTreeViewImpl(client)); + if (!layerTreeViewImpl->initialize(settings)) + return 0; + layerTreeViewImpl->setRootLayer(root); + return layerTreeViewImpl.leakPtr(); +} + +WebLayerTreeViewImpl::WebLayerTreeViewImpl(WebLayerTreeViewClient* client) + : m_client(client) +{ +} + +WebLayerTreeViewImpl::~WebLayerTreeViewImpl() +{ +} + +bool WebLayerTreeViewImpl::initialize(const WebLayerTreeView::Settings& webSettings) +{ + CCLayerTreeSettings settings; + settings.acceleratePainting = webSettings.acceleratePainting; + settings.showFPSCounter = webSettings.showFPSCounter; + settings.showPlatformLayerTree = webSettings.showPlatformLayerTree; + settings.showPaintRects = webSettings.showPaintRects; + settings.renderVSyncEnabled = webSettings.renderVSyncEnabled; + settings.refreshRate = webSettings.refreshRate; + settings.defaultTileSize = webSettings.defaultTileSize; + settings.maxUntiledLayerSize = webSettings.maxUntiledLayerSize; + m_layerTreeHost = CCLayerTreeHost::create(this, settings); + if (!m_layerTreeHost) + return false; + return true; +} + +void WebLayerTreeViewImpl::setSurfaceReady() +{ + m_layerTreeHost->setSurfaceReady(); +} + +void WebLayerTreeViewImpl::setRootLayer(const WebLayer& root) +{ + m_layerTreeHost->setRootLayer(static_cast<const WebLayerImpl*>(&root)->layer()); +} + +void WebLayerTreeViewImpl::clearRootLayer() +{ + m_layerTreeHost->setRootLayer(PassRefPtr<LayerChromium>()); +} + +int WebLayerTreeViewImpl::compositorIdentifier() +{ + return m_layerTreeHost->compositorIdentifier(); +} + +void WebLayerTreeViewImpl::setViewportSize(const WebSize& layoutViewportSize, const WebSize& deviceViewportSize) +{ + if (!deviceViewportSize.isEmpty()) + m_layerTreeHost->setViewportSize(layoutViewportSize, deviceViewportSize); + else + m_layerTreeHost->setViewportSize(layoutViewportSize, layoutViewportSize); +} + +WebSize WebLayerTreeViewImpl::layoutViewportSize() const +{ + return WebSize(m_layerTreeHost->layoutViewportSize()); +} + +WebSize WebLayerTreeViewImpl::deviceViewportSize() const +{ + return WebSize(m_layerTreeHost->deviceViewportSize()); +} + +void WebLayerTreeViewImpl::setDeviceScaleFactor(const float deviceScaleFactor) +{ + m_layerTreeHost->setDeviceScaleFactor(deviceScaleFactor); +} + +float WebLayerTreeViewImpl::deviceScaleFactor() const +{ + return m_layerTreeHost->deviceScaleFactor(); +} + +void WebLayerTreeViewImpl::setBackgroundColor(WebColor color) +{ + m_layerTreeHost->setBackgroundColor(color); +} + +void WebLayerTreeViewImpl::setHasTransparentBackground(bool transparent) +{ + m_layerTreeHost->setHasTransparentBackground(transparent); +} + +void WebLayerTreeViewImpl::setVisible(bool visible) +{ + m_layerTreeHost->setVisible(visible); +} + +void WebLayerTreeViewImpl::setPageScaleFactorAndLimits(float pageScaleFactor, float minimum, float maximum) +{ + m_layerTreeHost->setPageScaleFactorAndLimits(pageScaleFactor, minimum, maximum); +} + +void WebLayerTreeViewImpl::startPageScaleAnimation(const WebPoint& scroll, bool useAnchor, float newPageScale, double durationSec) +{ + m_layerTreeHost->startPageScaleAnimation(IntSize(scroll.x, scroll.y), useAnchor, newPageScale, durationSec); +} + +void WebLayerTreeViewImpl::setNeedsAnimate() +{ + m_layerTreeHost->setNeedsAnimate(); +} + +void WebLayerTreeViewImpl::setNeedsRedraw() +{ + m_layerTreeHost->setNeedsRedraw(); +} + +bool WebLayerTreeViewImpl::commitRequested() const +{ + return m_layerTreeHost->commitRequested(); +} + +void WebLayerTreeViewImpl::composite() +{ + if (CCProxy::hasImplThread()) + m_layerTreeHost->setNeedsCommit(); + else + m_layerTreeHost->composite(); +} + +void WebLayerTreeViewImpl::updateAnimations(double frameBeginTime) +{ + m_layerTreeHost->updateAnimations(frameBeginTime); +} + +bool WebLayerTreeViewImpl::compositeAndReadback(void *pixels, const WebRect& rect) +{ + return m_layerTreeHost->compositeAndReadback(pixels, rect); +} + +void WebLayerTreeViewImpl::finishAllRendering() +{ + m_layerTreeHost->finishAllRendering(); +} + +void WebLayerTreeViewImpl::renderingStats(WebRenderingStats& stats) const +{ + CCRenderingStats ccStats; + m_layerTreeHost->renderingStats(ccStats); + + stats.numAnimationFrames = ccStats.numAnimationFrames; + stats.numFramesSentToScreen = ccStats.numFramesSentToScreen; + stats.droppedFrameCount = ccStats.droppedFrameCount; + stats.totalPaintTimeInSeconds = ccStats.totalPaintTimeInSeconds; + stats.totalRasterizeTimeInSeconds = ccStats.totalRasterizeTimeInSeconds; +} + +void WebLayerTreeViewImpl::setFontAtlas(SkBitmap bitmap, WebRect asciiToWebRectTable[128], int fontHeight) +{ + IntRect asciiToRectTable[128]; + for (int i = 0; i < 128; ++i) + asciiToRectTable[i] = asciiToWebRectTable[i]; + OwnPtr<CCFontAtlas> fontAtlas = CCFontAtlas::create(bitmap, asciiToRectTable, fontHeight); + m_layerTreeHost->setFontAtlas(fontAtlas.release()); +} + +void WebLayerTreeViewImpl::loseCompositorContext(int numTimes) +{ + m_layerTreeHost->loseContext(numTimes); +} + +void WebLayerTreeViewImpl::willBeginFrame() +{ + m_client->willBeginFrame(); +} + +void WebLayerTreeViewImpl::didBeginFrame() +{ + m_client->didBeginFrame(); +} + +void WebLayerTreeViewImpl::animate(double monotonicFrameBeginTime) +{ + m_client->updateAnimations(monotonicFrameBeginTime); +} + +void WebLayerTreeViewImpl::layout() +{ + m_client->layout(); +} + +void WebLayerTreeViewImpl::applyScrollAndScale(const WebCore::IntSize& scrollDelta, float pageScale) +{ + m_client->applyScrollAndScale(scrollDelta, pageScale); +} + +PassOwnPtr<WebCompositorOutputSurface> WebLayerTreeViewImpl::createOutputSurface() +{ + return adoptPtr(m_client->createOutputSurface()); +} + +void WebLayerTreeViewImpl::didRecreateOutputSurface(bool success) +{ + m_client->didRecreateOutputSurface(success); +} + +void WebLayerTreeViewImpl::willCommit() +{ + m_client->willCommit(); +} + +void WebLayerTreeViewImpl::didCommit() +{ + m_client->didCommit(); +} + +void WebLayerTreeViewImpl::didCommitAndDrawFrame() +{ + m_client->didCommitAndDrawFrame(); +} + +void WebLayerTreeViewImpl::didCompleteSwapBuffers() +{ + m_client->didCompleteSwapBuffers(); +} + +void WebLayerTreeViewImpl::scheduleComposite() +{ + m_client->scheduleComposite(); +} + +} // namespace WebKit diff --git a/webkit/compositor/WebLayerTreeViewImpl.h b/webkit/compositor/WebLayerTreeViewImpl.h new file mode 100644 index 0000000..ac13759 --- /dev/null +++ b/webkit/compositor/WebLayerTreeViewImpl.h @@ -0,0 +1,72 @@ +// 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. + +#ifndef WebLayerTreeViewImpl_h +#define WebLayerTreeViewImpl_h + +#include "CCLayerTreeHost.h" +#include <public/WebLayerTreeView.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebKit { +class WebLayer; +class WebLayerTreeViewClient; +class WebLayerTreeViewClientAdapter; + +class WebLayerTreeViewImpl : public WebLayerTreeView, public WebCore::CCLayerTreeHostClient { +public: + explicit WebLayerTreeViewImpl(WebLayerTreeViewClient*); + virtual ~WebLayerTreeViewImpl(); + + bool initialize(const Settings&); + + // WebLayerTreeView implementation. + virtual void setSurfaceReady() OVERRIDE; + virtual void setRootLayer(const WebLayer&) OVERRIDE; + virtual void clearRootLayer() OVERRIDE; + virtual int compositorIdentifier() OVERRIDE; + virtual void setViewportSize(const WebSize& layoutViewportSize, const WebSize& deviceViewportSize = WebSize()) OVERRIDE; + virtual WebSize layoutViewportSize() const OVERRIDE; + virtual WebSize deviceViewportSize() const OVERRIDE; + virtual void setDeviceScaleFactor(float) OVERRIDE; + virtual float deviceScaleFactor() const OVERRIDE; + virtual void setBackgroundColor(WebColor) OVERRIDE; + virtual void setHasTransparentBackground(bool) OVERRIDE; + virtual void setVisible(bool) OVERRIDE; + virtual void setPageScaleFactorAndLimits(float pageScaleFactor, float minimum, float maximum) OVERRIDE; + virtual void startPageScaleAnimation(const WebPoint& destination, bool useAnchor, float newPageScale, double durationSec) OVERRIDE; + virtual void setNeedsAnimate() OVERRIDE; + virtual void setNeedsRedraw() OVERRIDE; + virtual bool commitRequested() const OVERRIDE; + virtual void composite() OVERRIDE; + virtual void updateAnimations(double frameBeginTime) OVERRIDE; + virtual bool compositeAndReadback(void *pixels, const WebRect&) OVERRIDE; + virtual void finishAllRendering() OVERRIDE; + virtual void renderingStats(WebRenderingStats&) const OVERRIDE; + virtual void setFontAtlas(SkBitmap, WebRect asciiToRectTable[128], int fontHeight) OVERRIDE; + virtual void loseCompositorContext(int numTimes) OVERRIDE; + + // WebCore::CCLayerTreeHostClient implementation. + virtual void willBeginFrame() OVERRIDE; + virtual void didBeginFrame() OVERRIDE; + virtual void animate(double monotonicFrameBeginTime) OVERRIDE; + virtual void layout() OVERRIDE; + virtual void applyScrollAndScale(const WebCore::IntSize& scrollDelta, float pageScale) OVERRIDE; + virtual PassOwnPtr<WebCompositorOutputSurface> createOutputSurface() OVERRIDE; + virtual void didRecreateOutputSurface(bool success) OVERRIDE; + virtual void willCommit() OVERRIDE; + virtual void didCommit() OVERRIDE; + virtual void didCommitAndDrawFrame() OVERRIDE; + virtual void didCompleteSwapBuffers() OVERRIDE; + virtual void scheduleComposite() OVERRIDE; + +private: + WebLayerTreeViewClient* m_client; + OwnPtr<WebCore::CCLayerTreeHost> m_layerTreeHost; +}; + +} // namespace WebKit + +#endif // WebLayerTreeViewImpl_h diff --git a/webkit/compositor/WebScrollbarLayerImpl.cpp b/webkit/compositor/WebScrollbarLayerImpl.cpp new file mode 100644 index 0000000..c4f2071 --- /dev/null +++ b/webkit/compositor/WebScrollbarLayerImpl.cpp @@ -0,0 +1,44 @@ +// Copyright 2012 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 "WebScrollbarLayerImpl.h" + +#include "ScrollbarLayerChromium.h" +#include "WebLayerImpl.h" + +using WebCore::Scrollbar; +using WebCore::ScrollbarLayerChromium; + +namespace WebKit { + +WebScrollbarLayer* WebScrollbarLayer::create(WebCore::Scrollbar* scrollbar, WebScrollbarThemePainter painter, PassOwnPtr<WebScrollbarThemeGeometry> geometry) +{ + return new WebScrollbarLayerImpl(ScrollbarLayerChromium::create(WebScrollbar::create(scrollbar), painter, geometry, 0)); +} + + +WebScrollbarLayerImpl::WebScrollbarLayerImpl(PassRefPtr<WebCore::ScrollbarLayerChromium> layer) + : m_layer(adoptPtr(new WebLayerImpl(layer))) +{ +} + +WebScrollbarLayerImpl::~WebScrollbarLayerImpl() +{ +} + +WebLayer* WebScrollbarLayerImpl::layer() +{ + return m_layer.get(); +} + +void WebScrollbarLayerImpl::setScrollLayer(WebLayer* layer) +{ + int id = layer ? static_cast<WebLayerImpl*>(layer)->layer()->id() : 0; + static_cast<ScrollbarLayerChromium*>(m_layer->layer())->setScrollLayerId(id); +} + + + +} // namespace WebKit diff --git a/webkit/compositor/WebScrollbarLayerImpl.h b/webkit/compositor/WebScrollbarLayerImpl.h new file mode 100644 index 0000000..3af38f2 --- /dev/null +++ b/webkit/compositor/WebScrollbarLayerImpl.h @@ -0,0 +1,34 @@ +// Copyright 2012 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. + +#ifndef WebScrollbarLayerImpl_h +#define WebScrollbarLayerImpl_h + +#include <public/WebScrollbarLayer.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { +class ScrollbarLayerChromium; +} + +namespace WebKit { +class WebLayerImpl; + +class WebScrollbarLayerImpl : public WebScrollbarLayer { +public: + explicit WebScrollbarLayerImpl(PassRefPtr<WebCore::ScrollbarLayerChromium>); + virtual ~WebScrollbarLayerImpl(); + + // WebScrollbarLayer implementation. + virtual WebLayer* layer() OVERRIDE; + virtual void setScrollLayer(WebLayer*) OVERRIDE; + +private: + OwnPtr<WebLayerImpl> m_layer; +}; + +} + +#endif // WebScrollbarLayerImpl_h diff --git a/webkit/compositor/WebSolidColorLayerImpl.cpp b/webkit/compositor/WebSolidColorLayerImpl.cpp new file mode 100644 index 0000000..51be974 --- /dev/null +++ b/webkit/compositor/WebSolidColorLayerImpl.cpp @@ -0,0 +1,41 @@ +// Copyright 2012 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 "WebSolidColorLayerImpl.h" + +#include "SolidColorLayerChromium.h" +#include "WebLayerImpl.h" + +using WebCore::SolidColorLayerChromium; + +namespace WebKit { + +WebSolidColorLayer* WebSolidColorLayer::create() +{ + return new WebSolidColorLayerImpl(SolidColorLayerChromium::create()); +} + +WebSolidColorLayerImpl::WebSolidColorLayerImpl(PassRefPtr<SolidColorLayerChromium> layer) + : m_layer(adoptPtr(new WebLayerImpl(layer))) +{ + m_layer->layer()->setIsDrawable(true); +} + +WebSolidColorLayerImpl::~WebSolidColorLayerImpl() +{ +} + +WebLayer* WebSolidColorLayerImpl::layer() +{ + return m_layer.get(); +} + +void WebSolidColorLayerImpl::setBackgroundColor(WebColor color) +{ + m_layer->setBackgroundColor(color); +} + +} // namespace WebKit + diff --git a/webkit/compositor/WebSolidColorLayerImpl.h b/webkit/compositor/WebSolidColorLayerImpl.h new file mode 100644 index 0000000..b4a83dc --- /dev/null +++ b/webkit/compositor/WebSolidColorLayerImpl.h @@ -0,0 +1,35 @@ +// Copyright 2012 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. + +#ifndef WebSolidColorLayerImpl_h +#define WebSolidColorLayerImpl_h + +#include <public/WebSolidColorLayer.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { +class SolidColorLayerChromium; +} + +namespace WebKit { +class WebLayerImpl; + +class WebSolidColorLayerImpl : public WebSolidColorLayer { +public: + explicit WebSolidColorLayerImpl(PassRefPtr<WebCore::SolidColorLayerChromium>); + virtual ~WebSolidColorLayerImpl(); + + // WebSolidColorLayer implementation. + virtual WebLayer* layer() OVERRIDE; + virtual void setBackgroundColor(WebColor) OVERRIDE; + +private: + OwnPtr<WebLayerImpl> m_layer; +}; + +} // namespace WebKit + +#endif // WebSolidColorLayerImpl_h + diff --git a/webkit/compositor/WebTransformAnimationCurveImpl.cpp b/webkit/compositor/WebTransformAnimationCurveImpl.cpp new file mode 100644 index 0000000..f1efd1f --- /dev/null +++ b/webkit/compositor/WebTransformAnimationCurveImpl.cpp @@ -0,0 +1,61 @@ +// Copyright 2012 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 "WebTransformAnimationCurveImpl.h" + +#include "CCKeyframedAnimationCurve.h" +#include "CCTimingFunction.h" +#include "WebAnimationCurveCommon.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebKit { + +WebTransformAnimationCurve* WebTransformAnimationCurve::create() +{ + return new WebTransformAnimationCurveImpl(WebCore::CCKeyframedTransformAnimationCurve::create()); +} + +WebTransformAnimationCurveImpl::WebTransformAnimationCurveImpl(PassOwnPtr<WebCore::CCKeyframedTransformAnimationCurve> curve) + : m_curve(curve) +{ +} + +WebTransformAnimationCurveImpl::~WebTransformAnimationCurveImpl() +{ +} + +WebAnimationCurve::AnimationCurveType WebTransformAnimationCurveImpl::type() const +{ + return WebAnimationCurve::AnimationCurveTypeTransform; +} + +void WebTransformAnimationCurveImpl::add(const WebTransformKeyframe& keyframe) +{ + add(keyframe, TimingFunctionTypeEase); +} + +void WebTransformAnimationCurveImpl::add(const WebTransformKeyframe& keyframe, TimingFunctionType type) +{ + m_curve->addKeyframe(WebCore::CCTransformKeyframe::create(keyframe.time, keyframe.value, createTimingFunction(type))); +} + +void WebTransformAnimationCurveImpl::add(const WebTransformKeyframe& keyframe, double x1, double y1, double x2, double y2) +{ + m_curve->addKeyframe(WebCore::CCTransformKeyframe::create(keyframe.time, keyframe.value, WebCore::CCCubicBezierTimingFunction::create(x1, y1, x2, y2))); +} + +WebTransformationMatrix WebTransformAnimationCurveImpl::getValue(double time) const +{ + return m_curve->getValue(time); +} + +PassOwnPtr<WebCore::CCAnimationCurve> WebTransformAnimationCurveImpl::cloneToCCAnimationCurve() const +{ + return m_curve->clone(); +} + +} // namespace WebKit diff --git a/webkit/compositor/WebTransformAnimationCurveImpl.h b/webkit/compositor/WebTransformAnimationCurveImpl.h new file mode 100644 index 0000000..e65e163 --- /dev/null +++ b/webkit/compositor/WebTransformAnimationCurveImpl.h @@ -0,0 +1,42 @@ +// Copyright 2012 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. + +#ifndef WebTransformAnimationCurveImpl_h +#define WebTransformAnimationCurveImpl_h + +#include <public/WebTransformAnimationCurve.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { +class CCAnimationCurve; +class CCKeyframedTransformAnimationCurve; +} + +namespace WebKit { + +class WebTransformAnimationCurveImpl : public WebTransformAnimationCurve { +public: + explicit WebTransformAnimationCurveImpl(PassOwnPtr<WebCore::CCKeyframedTransformAnimationCurve>); + virtual ~WebTransformAnimationCurveImpl(); + + // WebAnimationCurve implementation. + virtual AnimationCurveType type() const OVERRIDE; + + // WebTransformAnimationCurve implementation. + virtual void add(const WebTransformKeyframe&) OVERRIDE; + virtual void add(const WebTransformKeyframe&, TimingFunctionType) OVERRIDE; + virtual void add(const WebTransformKeyframe&, double x1, double y1, double x2, double y2) OVERRIDE; + + virtual WebTransformationMatrix getValue(double time) const OVERRIDE; + + PassOwnPtr<WebCore::CCAnimationCurve> cloneToCCAnimationCurve() const; + +private: + OwnPtr<WebCore::CCKeyframedTransformAnimationCurve> m_curve; +}; + +} + +#endif // WebTransformAnimationCurveImpl_h diff --git a/webkit/compositor/WebVideoLayerImpl.cpp b/webkit/compositor/WebVideoLayerImpl.cpp new file mode 100644 index 0000000..bddba88 --- /dev/null +++ b/webkit/compositor/WebVideoLayerImpl.cpp @@ -0,0 +1,37 @@ +// 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 "config.h" +#include "WebVideoLayerImpl.h" + +#include "VideoLayerChromium.h" +#include "WebLayerImpl.h" + +namespace WebKit { + +WebVideoLayer* WebVideoLayer::create(WebVideoFrameProvider* provider) +{ + return new WebVideoLayerImpl(WebCore::VideoLayerChromium::create(provider)); +} + +WebVideoLayerImpl::WebVideoLayerImpl(PassRefPtr<WebCore::VideoLayerChromium> layer) + : m_layer(adoptPtr(new WebLayerImpl(layer))) +{ +} + +WebVideoLayerImpl::~WebVideoLayerImpl() +{ +} + +WebLayer* WebVideoLayerImpl::layer() +{ + return m_layer.get(); +} + +bool WebVideoLayerImpl::active() const +{ + return m_layer->layer()->layerTreeHost(); +} + +} // namespace WebKit diff --git a/webkit/compositor/WebVideoLayerImpl.h b/webkit/compositor/WebVideoLayerImpl.h new file mode 100644 index 0000000..d8935bd5 --- /dev/null +++ b/webkit/compositor/WebVideoLayerImpl.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef WebVideoLayerImpl_h +#define WebVideoLayerImpl_h + +#include <public/WebVideoLayer.h> + +namespace WebCore { +class VideoLayerChromium; +} + +namespace WebKit { +class WebLayerImpl; + +class WebVideoLayerImpl : public WebVideoLayer { +public: + explicit WebVideoLayerImpl(PassRefPtr<WebCore::VideoLayerChromium>); + virtual ~WebVideoLayerImpl(); + + // WebVideoLayer implementation. + virtual WebLayer* layer() OVERRIDE; + virtual bool active() const OVERRIDE; + +private: + OwnPtr<WebLayerImpl> m_layer; +}; + +} + +#endif // WebVideoLayerImpl_h + diff --git a/webkit/compositor/WheelFlingPlatformGestureCurve.h b/webkit/compositor/WheelFlingPlatformGestureCurve.h new file mode 100644 index 0000000..2ff1f46 --- /dev/null +++ b/webkit/compositor/WheelFlingPlatformGestureCurve.h @@ -0,0 +1,38 @@ +// Copyright 2012 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. + +#ifndef WheelFlingPlatformGestureCurve_h +#define WheelFlingPlatformGestureCurve_h + +#include "FloatPoint.h" +#include "PlatformGestureCurve.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +class PlatformGestureCurveTarget; + +// Implementation of PlatformGestureCurve suitable for mouse wheel-based fling +// scroll. A Rayleigh distribtution curve is used to define the velocity profile, +// so velocity starts at zero, accelerates to a maximum proportional to 'velocity', +// then gently tails off to zero again. +class WheelFlingPlatformGestureCurve : public PlatformGestureCurve { +public: + static PassOwnPtr<PlatformGestureCurve> create(const FloatPoint& velocity); + virtual ~WheelFlingPlatformGestureCurve(); + + virtual const char* debugName() const OVERRIDE; + virtual bool apply(double time, PlatformGestureCurveTarget*) OVERRIDE; + +private: + explicit WheelFlingPlatformGestureCurve(const FloatPoint& velocity); + + FloatPoint m_velocity; + IntPoint m_cumulativeScroll; +}; + +} // namespace WebCore + +#endif diff --git a/webkit/compositor/compositor.gyp b/webkit/compositor/compositor.gyp new file mode 100644 index 0000000..c6f4d4e --- /dev/null +++ b/webkit/compositor/compositor.gyp @@ -0,0 +1,79 @@ +# Copyright (c) 2012 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. + +{ + 'variables': { + 'chromium_code': 0, + 'webkit_compositor_sources': [ + 'CCThreadImpl.cpp', + 'CCThreadImpl.h', + 'PlatformGestureCurve.h', + 'PlatformGestureCurveTarget.h', + 'TouchpadFlingPlatformGestureCurve.h', + 'WebAnimationCurveCommon.cpp', + 'WebAnimationCurveCommon.h', + 'WebAnimationImpl.cpp', + 'WebAnimationImpl.h', + 'WebCompositorImpl.cpp', + 'WebCompositorImpl.h', + 'WebCompositorInputHandlerImpl.cpp', + 'WebCompositorInputHandlerImpl.h', + 'WebContentLayerImpl.cpp', + 'WebContentLayerImpl.h', + 'WebExternalTextureLayerImpl.cpp', + 'WebExternalTextureLayerImpl.h', + 'WebFloatAnimationCurveImpl.cpp', + 'WebFloatAnimationCurveImpl.h', + 'WebIOSurfaceLayerImpl.cpp', + 'WebIOSurfaceLayerImpl.h', + 'WebImageLayerImpl.cpp', + 'WebImageLayerImpl.h', + 'WebLayerImpl.cpp', + 'WebLayerImpl.h', + 'WebLayerTreeViewImpl.cpp', + 'WebLayerTreeViewImpl.h', + 'WebScrollbarLayerImpl.cpp', + 'WebScrollbarLayerImpl.h', + 'WebSolidColorLayerImpl.cpp', + 'WebSolidColorLayerImpl.h', + 'WebTransformAnimationCurveImpl.cpp', + 'WebTransformAnimationCurveImpl.h', + 'WebVideoLayerImpl.cpp', + 'WebVideoLayerImpl.h', + 'WheelFlingPlatformGestureCurve.h', + ], + }, + 'conditions': [ + ['use_libcc_for_compositor==1', { + 'targets': [ + { + 'target_name': 'webkit_compositor', + 'type': 'static_library', + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/cc/cc.gyp:cc', + '<(DEPTH)/skia/skia.gyp:skia', + '<(DEPTH)/third_party/WebKit/Source/Platform/Platform.gyp/Platform.gyp:webkit_platform', + # We have to depend on WTF directly to pick up the correct defines for WTF headers - for instance USE_SYSTEM_MALLOC. + '<(DEPTH)/third_party/WebKit/Source/WTF/WTF.gyp/WTF.gyp:wtf', + ], + 'defines': [ + 'WEBKIT_IMPLEMENTATION=1', + ], + 'include_dirs': [ + 'stubs', + '<(DEPTH)/cc', + '<(DEPTH)/cc/stubs', + '<(DEPTH)/third_party/WebKit/Source/WebKit/chromium/public', + ], + 'sources': [ + '<@(webkit_compositor_sources)', + 'stubs/AnimationIdVendor.h', + 'stubs/public/WebTransformationMatrix', + ], + }, + ], + }], + ], +} diff --git a/webkit/compositor/copyfiles.py b/webkit/compositor/copyfiles.py new file mode 100644 index 0000000..6b94a19 --- /dev/null +++ b/webkit/compositor/copyfiles.py @@ -0,0 +1,69 @@ +# Copyright (c) 2012 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. + +import shutil +import os +import re + +prefixes = ["../../third_party/WebKit/Source/WebCore/platform/chromium/support", + "../../third_party/WebKit/Source/WebKit/chromium/src", + "../../third_party/WebKit/Source/WebCore/platform"] + +def Copy(name): + src = name + dst = name + fullsrc = "" + for prefix in prefixes: + candidate = "%s/%s" % (prefix, src) + if os.path.exists(candidate): + fullsrc = candidate + break + assert fullsrc != "" + shutil.copyfile(fullsrc, dst) + print "copying from %s to %s" % (fullsrc, dst) + return dst + +def Readfile(gypfile): + cc_gyp = open(gypfile, "r") + obj = eval(cc_gyp.read()) + cc_gyp.close() + return obj + +def FixCopyrightHeaderText(text, year): + header_template = """// Copyright %s 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. +""" + + while text[0].find(" */") == -1: + text = text[1:] + text = text[1:] + + return (header_template % year) + "".join(text) + +def FixCopyrightHeader(filepath): + with open(filepath, "r") as f: + text = f.readlines() + + pattern = ".*Copyright \(C\) (20[01][0-9])" + m = re.match(pattern, text[0]) + if m == None: + m = re.match(pattern, text[1]) + assert m + year = m.group(1) + + fixed_text = FixCopyrightHeaderText(text, year) + with open(filepath, "w") as f: + f.write(fixed_text) + +def Main(): + files = Readfile("compositor.gyp")['variables']['webkit_compositor_sources'] + for f in files: + dst =Copy(f) + FixCopyrightHeader(dst) + +if __name__ == '__main__': + import sys + os.chdir(os.path.dirname(__file__)) + sys.exit(Main()) diff --git a/webkit/compositor/stubs/public/WebTransformationMatrix.h b/webkit/compositor/stubs/public/WebTransformationMatrix.h new file mode 100644 index 0000000..2c6ed7c --- /dev/null +++ b/webkit/compositor/stubs/public/WebTransformationMatrix.h @@ -0,0 +1,13 @@ +// Copyright 2012 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. + +#ifndef WEBKIT_COMPOSITOR_STUBS_WEBTRANSFORMATIONMATRIX_H_ +#define WEBKIT_COMPOSITOR_STUBS_WEBTRANSFORMATIONMATRIX_H_ + +#undef WEBKIT_IMPLEMENTATION +#include "third_party/WebKit/Source/Platform/chromium/public/WebTransformationMatrix.h" +#undef WEBKIT_IMPLEMENTATION +#define WEBKIT_IMPLEMENTATION 1 + +#endif // WEBKIT_COMPOSITOR_STUBS_WEBTRANSFORMATIONMATRIX_H_ |