Import Cobalt 9.140119
Change-Id: Idfa97844364064c39ec81cbe3f998a22b7009be6
diff --git a/src/cobalt/base/c_val.cc b/src/cobalt/base/c_val.cc
index f0c9a84..d44f9fa 100644
--- a/src/cobalt/base/c_val.cc
+++ b/src/cobalt/base/c_val.cc
@@ -48,7 +48,9 @@
delete registered_vars_;
}
-void CValManager::RegisterCVal(const CValDetail::CValBase* cval) {
+void CValManager::RegisterCVal(
+ const CValDetail::CValBase* cval,
+ scoped_refptr<base::RefCountedThreadSafeLock>* value_lock) {
base::AutoLock auto_lock(cvals_lock_);
// CVals cannot share name. If this assert is triggered, we are trying to
@@ -57,6 +59,7 @@
DCHECK(registered_vars_->find(cval->GetName()) == registered_vars_->end());
(*registered_vars_)[cval->GetName()] = cval;
+ *value_lock = value_lock_refptr_;
}
void CValManager::UnregisterCVal(const CValDetail::CValBase* cval) {
diff --git a/src/cobalt/base/c_val.h b/src/cobalt/base/c_val.h
index 92f8f6c..e85b935 100644
--- a/src/cobalt/base/c_val.h
+++ b/src/cobalt/base/c_val.h
@@ -500,8 +500,10 @@
~CValManager();
// Called in CVal constructors to register/deregister themselves with the
- // system.
- void RegisterCVal(const CValDetail::CValBase* cval);
+ // system. Registering a CVal will also provide that CVal with a value lock
+ // to lock when it modifies its value.
+ void RegisterCVal(const CValDetail::CValBase* cval,
+ scoped_refptr<base::RefCountedThreadSafeLock>* value_lock);
void UnregisterCVal(const CValDetail::CValBase* cval);
#if defined(ENABLE_DEBUG_C_VAL)
@@ -586,7 +588,6 @@
CommonConstructor();
}
virtual ~CValImpl() {
- base::AutoLock auto_lock(value_lock_refptr_->GetLock());
if (registered_) {
CValManager::GetInstance()->UnregisterCVal(this);
}
@@ -673,8 +674,7 @@
void RegisterWithManager() {
if (!registered_) {
CValManager* manager = CValManager::GetInstance();
- manager->RegisterCVal(this);
- value_lock_refptr_ = manager->value_lock_refptr_;
+ manager->RegisterCVal(this, &value_lock_refptr_);
registered_ = true;
}
}
diff --git a/src/cobalt/base/c_val_collection_timer_stats.h b/src/cobalt/base/c_val_collection_timer_stats.h
index eda96b9..4f21cb7 100644
--- a/src/cobalt/base/c_val_collection_timer_stats.h
+++ b/src/cobalt/base/c_val_collection_timer_stats.h
@@ -63,6 +63,9 @@
start_time_ = base::TimeTicks();
}
+ // Stops any started timer and this sample is ignored.
+ void Cancel() { start_time_ = base::TimeTicks(); }
+
// Flush the collection. This causes |CValCollectionStats| to update its stats
// and clear the entries.
void Flush() { entry_stats_.Flush(); }
diff --git a/src/cobalt/base/c_val_test.cc b/src/cobalt/base/c_val_test.cc
index 3dc416d..6281766 100644
--- a/src/cobalt/base/c_val_test.cc
+++ b/src/cobalt/base/c_val_test.cc
@@ -13,7 +13,10 @@
// limitations under the License.
#include <limits>
+#include <utility>
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
#include "base/time.h"
#include "cobalt/base/c_val.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -584,4 +587,113 @@
EXPECT_EQ(test_hook.value_changed_count_, 1);
}
+namespace {
+// Helper class for the RemoveAndRead test defined below.
+class ReadCValThread : public base::SimpleThread {
+ public:
+ ReadCValThread(const char* test_cval_name, int num_read_cycles,
+ std::pair<std::string, std::string> valid_values,
+ base::WaitableEvent* thread_ready_event,
+ base::WaitableEvent* start_processing_event)
+ : base::SimpleThread("ReadCValThread"),
+ test_cval_name_(test_cval_name),
+ num_read_cycles_(num_read_cycles),
+ valid_values_(valid_values),
+ thread_ready_event_(thread_ready_event),
+ start_processing_event_(start_processing_event) {}
+
+ void Run() OVERRIDE {
+ thread_ready_event_->Signal();
+ start_processing_event_->Wait();
+
+ base::CValManager* cvm = base::CValManager::GetInstance();
+ for (int i = 0; i < num_read_cycles_; ++i) {
+ base::optional<std::string> result =
+ cvm->GetValueAsString(test_cval_name_);
+ if (result) {
+ EXPECT_TRUE(*result == valid_values_.first ||
+ *result == valid_values_.second);
+ }
+ }
+ }
+
+ private:
+ const char* test_cval_name_;
+ int num_read_cycles_;
+ std::pair<std::string, std::string> valid_values_;
+ base::WaitableEvent* thread_ready_event_;
+ base::WaitableEvent* start_processing_event_;
+};
+
+// Helper class for the RemoveAndRead test defined below.
+class CreateDestroyCValThread : public base::SimpleThread {
+ public:
+ CreateDestroyCValThread(const char* test_cval_name,
+ int num_create_destroy_cycles,
+ std::pair<std::string, std::string> valid_values,
+ base::WaitableEvent* thread_ready_event,
+ base::WaitableEvent* start_processing_event)
+ : base::SimpleThread("CreateDestroyCValThread"),
+ test_cval_name_(test_cval_name),
+ num_create_destroy_cycles_(num_create_destroy_cycles),
+ valid_values_(valid_values),
+ thread_ready_event_(thread_ready_event),
+ start_processing_event_(start_processing_event) {}
+
+ void Run() OVERRIDE {
+ thread_ready_event_->Signal();
+ start_processing_event_->Wait();
+
+ for (int i = 0; i < num_create_destroy_cycles_; ++i) {
+ base::CVal<std::string> test_cval(test_cval_name_, valid_values_.first,
+ "Description");
+ test_cval = valid_values_.second;
+ }
+ }
+
+ private:
+ const char* test_cval_name_;
+ int num_create_destroy_cycles_;
+ std::pair<std::string, std::string> valid_values_;
+ base::WaitableEvent* thread_ready_event_;
+ base::WaitableEvent* start_processing_event_;
+};
+
+} // namespace
+
+// Tests that we can create and destroy cvals no problem while simultaneously
+// reading from them. The test creates two threads, a reader thread and a
+// creater/destroyer (and writer) thread. These both attempt to access the same
+// cval as fast as possible.
+TEST(CValTest, RemoveAndRead) {
+ const char* kTestCValName = "TestCVal";
+ const int kNumReadCycles = 10000;
+ const int kNuMCreateDestroyCycles = 10000;
+ const std::pair<std::string, std::string> valid_values("hello", "66");
+ base::WaitableEvent read_cval_thread_ready(true, false);
+ base::WaitableEvent create_destroy_cval_thread_ready(true, false);
+ base::WaitableEvent start_processing_event(true, false);
+
+ // Create and start both threads.
+ ReadCValThread read_cval_thread(kTestCValName, kNumReadCycles, valid_values,
+ &read_cval_thread_ready,
+ &start_processing_event);
+ CreateDestroyCValThread create_destroy_cval_thread(
+ kTestCValName, kNuMCreateDestroyCycles, valid_values,
+ &create_destroy_cval_thread_ready, &start_processing_event);
+ read_cval_thread.Start();
+ create_destroy_cval_thread.Start();
+
+ // Wait until both threads are initialized and ready.
+ read_cval_thread_ready.Wait();
+ create_destroy_cval_thread_ready.Wait();
+
+ // Signal for the processing/testing to begin.
+ start_processing_event.Signal();
+
+ // Wait for both threads to complete.
+ read_cval_thread.Join();
+ create_destroy_cval_thread.Join();
+}
+
} // namespace base
diff --git a/src/cobalt/base/clock.h b/src/cobalt/base/clock.h
index 01d5442..e2a4bbc 100644
--- a/src/cobalt/base/clock.h
+++ b/src/cobalt/base/clock.h
@@ -49,6 +49,31 @@
~SystemMonotonicClock() OVERRIDE {}
};
+// The MinimumResolutionClock modifies the output of an existing clock by
+// clamping its minimum resolution to a predefined amount. This is implemented
+// by rounding down the existing clock's time to the previous multiple of the
+// desired clock resolution.
+class MinimumResolutionClock : public Clock {
+ public:
+ MinimumResolutionClock(scoped_refptr<Clock> parent,
+ const base::TimeDelta& min_resolution)
+ : parent_(parent),
+ min_resolution_in_microseconds_(min_resolution.InMicroseconds()) {
+ DCHECK(parent);
+ }
+
+ base::TimeDelta Now() OVERRIDE {
+ base::TimeDelta now = parent_->Now();
+ int64 microseconds = now.InMicroseconds();
+ return base::TimeDelta::FromMicroseconds(
+ microseconds - (microseconds % min_resolution_in_microseconds_));
+ }
+
+ private:
+ scoped_refptr<Clock> parent_;
+ const int64_t min_resolution_in_microseconds_;
+};
+
// The OffsetClock takes a parent clock and an offset upon construction, and
// when queried for the time it returns the time of the parent clock offset by
// the specified offset.
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 236fcef..29b055b 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -317,6 +317,8 @@
"h5vcc-location-src "
"https://www.youtube.com/tv "
"https://www.youtube.com/tv/ "
+ "https://web-green-qa.youtube.com/tv "
+ "https://web-green-qa.youtube.com/tv/ "
"https://web-release-qa.youtube.com/tv "
"https://web-release-qa.youtube.com/tv/ "
#if defined(ENABLE_ABOUT_SCHEME)
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index b916179..b575c58 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -624,7 +624,7 @@
web_module_stat_tracker_->OnEndInjectEvent(
window_->HasPendingAnimationFrameCallbacks(),
- layout_manager_->IsNewRenderTreePending());
+ layout_manager_->IsRenderTreePending());
}
void WebModule::Impl::ExecuteJavascript(
@@ -657,7 +657,7 @@
// Notify the stat tracker that the animation frame callbacks have finished.
// This may end the current event being tracked.
web_module_stat_tracker_->OnRanAnimationFrameCallbacks(
- layout_manager_->IsNewRenderTreePending());
+ layout_manager_->IsRenderTreePending());
}
void WebModule::Impl::OnRenderTreeProduced(
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 05f7d20..2f807b4 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-96542
\ No newline at end of file
+140119
\ No newline at end of file
diff --git a/src/cobalt/build/config/base.gypi b/src/cobalt/build/config/base.gypi
index ee9c278..da47e25 100644
--- a/src/cobalt/build/config/base.gypi
+++ b/src/cobalt/build/config/base.gypi
@@ -47,38 +47,22 @@
# Contains the current font package selection. This can be used to trade
# font quality, coverage, and latency for different font package sizes.
# The font package can be one of the following options:
- # 'expanded' -- The largest package. It includes everything in the
- # 'standard' package, along with 'bold' weight CJK. It is
- # recommended that 'local_font_cache_size_in_bytes' be
- # increased to 24MB when using this package to account for
- # the extra memory required by bold CJK. This package is
- # ~48.7MB.
# 'standard' -- The default package. It includes all sans-serif, serif,
# and FCC fonts, non-CJK fallback fonts in both 'normal' and
- # 'bold' weights, and 'normal' weight CJK ('bold' weight CJK
- # is synthesized from it). This package is ~29.4MB.
- # 'limited_with_jp' -- A significantly smaller package than 'standard'.
- # This package removes all but 'normal' and 'bold' weighted
+ # 'bold' weights, and 'normal' weight CJK ('bold' weight
+ # CJK is synthesized from it). This package is ~29.4MB.
+ # 'limited' -- A significantly smaller package than 'standard'. This
+ # package removes all but 'normal' and 'bold' weighted
# sans-serif and serif, removes the FCC fonts (which must be
# provided by the system or downloaded from the web),
- # removes the 'bold' weighted non-CJK fallback fonts (the
- # 'normal' weight is still included and is used to
- # synthesize bold), and replaces standard CJK with low
- # quality CJK. However, higher quality Japanese is still
- # included. Because low quality CJK cannot synthesize bold,
- # bold glyphs are unavailable in Chinese and Korean. This
- # package is ~10.9MB.
- # 'limited' -- A smaller package than 'limited_with_jp'. The two packages
- # are identical with the exception that 'limited' does not
- # include the higher quality Japanese font; instead it
- # relies on low quality CJK for all CJK characters. Because
+ # and replaces standard CJK with low quality CJK. Because
# low quality CJK cannot synthesize bold, bold glyphs are
- # unavailable in Chinese, Japanese, and Korean. This package
+ # unavailable in Chinese, Japanese and Korean. This package
# is ~7.7MB.
# 'minimal' -- The smallest possible font package. It only includes
# Roboto's Basic Latin characters. Everything else must be
# provided by the system or downloaded from the web. This
- # package is ~16.4KB.
+ # package is ~35.4KB.
# NOTE: When bold is needed, but unavailable, it is typically synthesized,
# resulting in lower quality glyphs than those generated directly from
# a bold font. However, this does not occur with low quality CJK,
@@ -111,7 +95,6 @@
'cobalt_font_package_override_fallback_lang_non_cjk%': -1,
'cobalt_font_package_override_fallback_lang_cjk%': -1,
'cobalt_font_package_override_fallback_lang_cjk_low_quality%': -1,
- 'cobalt_font_package_override_fallback_lang_jp%': -1,
'cobalt_font_package_override_fallback_emoji%': -1,
'cobalt_font_package_override_fallback_symbols%': -1,
diff --git a/src/cobalt/content/fonts/README.md b/src/cobalt/content/fonts/README.md
index 1181646..0c2edfc 100644
--- a/src/cobalt/content/fonts/README.md
+++ b/src/cobalt/content/fonts/README.md
@@ -41,23 +41,6 @@
}
### Package Profiles
-* 'expanded' -- The largest package. It includes everything in the 'standard'
- package, along with 'bold' weight CJK. It is recommended that
- 'local_font_cache_size_in_bytes' be increased to 24MB when
- using this package to account for the extra memory required by
- bold CJK. This package is ~48.7MB.
-
- Package category values:
- 'package_named_sans_serif': 4,
- 'package_named_serif': 3,
- 'package_named_fcc_fonts': 2,
- 'package_fallback_lang_non_cjk': 2,
- 'package_fallback_lang_cjk': 2,
- 'package_fallback_lang_cjk_low_quality': 0,
- 'package_fallback_lang_jp': 0,
- 'package_fallback_emoji': 1,
- 'package_fallback_symbols': 1,
-
* 'standard' -- The default package. It includes all sans-serif, serif, and FCC
fonts, non-CJK fallback fonts in both 'normal' and 'bold'
weights, and 'normal' weight CJK ('bold' weight CJK is
@@ -70,20 +53,16 @@
'package_fallback_lang_non_cjk': 2,
'package_fallback_lang_cjk': 1,
'package_fallback_lang_cjk_low_quality': 0,
- 'package_fallback_lang_jp': 0,
'package_fallback_emoji': 1,
'package_fallback_symbols': 1,
-* 'limited_with_jp' -- A significantly smaller package than 'standard'. This
- package removes all but 'normal' and 'bold' weighted sans-serif
- and serif, removes the FCC fonts (which must be provided by the
- system or downloaded from the web), removes the 'bold' weighted
- non-CJK fallback fonts (the 'normal' weight is still included
- and is used to synthesize bold), and replaces standard CJK with
- low quality CJK. However, higher quality Japanese is still
- included. Because low quality CJK cannot synthesize bold, bold
- glyphs are unavailable in Chinese and Korean. This package is
- ~10.9MB.
+* 'limited' -- A significantly smaller package than 'standard'. This package
+ removes all but 'normal' and 'bold' weighted sans-serif and
+ serif, removes the FCC fonts (which must be provided by the
+ system or downloaded from the web), and replaces standard CJK
+ with low quality CJK. Because low quality CJK cannot synthesize
+ bold, bold glyphs are unavailable in Chinese, Japanese and
+ Korean. This package is ~7.7MB.
Package category values:
'package_named_sans_serif': 2,
@@ -92,31 +71,12 @@
'package_fallback_lang_non_cjk': 1,
'package_fallback_lang_cjk': 0,
'package_fallback_lang_cjk_low_quality': 1,
- 'package_fallback_lang_jp': 1,
- 'package_fallback_emoji': 1,
- 'package_fallback_symbols': 1,
-
-* 'limited' -- A smaller package than 'limited_with_jp'. The two packages are
- identical with the exception that 'limited' does not include
- the higher quality Japanese font; instead it relies on low
- quality CJK for all CJK characters. Because low quality CJK
- cannot synthesize bold, bold glyphs are unavailable in Chinese,
- Japanese, and Korean. This package is ~7.7MB.
-
- Package category values:
- 'package_named_sans_serif': 2,
- 'package_named_serif': 0,
- 'package_named_fcc_fonts': 0,
- 'package_fallback_lang_non_cjk': 1,
- 'package_fallback_lang_cjk': 0,
- 'package_fallback_lang_cjk_low_quality': 1,
- 'package_fallback_lang_jp': 0,
'package_fallback_emoji': 1,
'package_fallback_symbols': 1,
* 'minimal' -- The smallest possible font package. It only includes Roboto's
Basic Latin characters. Everything else must be provided by the
- system or downloaded from the web. This package is ~16.4KB.
+ system or downloaded from the web. This package is ~35.4KB.
Package category values:
'package_named_sans_serif': 0,
@@ -125,7 +85,6 @@
'package_fallback_lang_non_cjk': 0,
'package_fallback_lang_cjk': 0,
'package_fallback_lang_cjk_low_quality': 0,
- 'package_fallback_lang_jp': 0,
'package_fallback_emoji': 0,
'package_fallback_symbols': 0,
@@ -160,10 +119,6 @@
included when 'package_fallback_lang_cjk' has a value of '0'. This is the
only category of fonts that is not synthetically boldable.
- * 'package_fallback_lang_jp':
- Higher quality Japanese language-specific fallback fonts. These should
- only be included when 'package_fallback_lang_cjk' has a value of '0'.
-
* 'package_fallback_emoji':
Emoji-related fallback fonts.
@@ -219,8 +174,6 @@
'package_fallback_lang_cjk'
* 'cobalt_font_package_override_fallback_lang_cjk_low_quality' ==>
'package_fallback_lang_cjk_low_quality'
- * 'cobalt_font_package_override_fallback_lang_jp' ==>
- 'package_fallback_lang_jp'
* 'cobalt_font_package_override_fallback_emoji' ==>
'package_fallback_emoji'
* 'cobalt_font_package_override_fallback_symbols' ==>
diff --git a/src/cobalt/content/fonts/config/common/fonts.xml b/src/cobalt/content/fonts/config/common/fonts.xml
index b60cc92..fc579cc 100644
--- a/src/cobalt/content/fonts/config/common/fonts.xml
+++ b/src/cobalt/content/fonts/config/common/fonts.xml
@@ -346,9 +346,6 @@
<family lang="ko" pages="0-4,17,30,32-39,41,43,46-159,169,172-215,249-251,254-255,497-498,512-658,660-682,685,687,689,691-696,698-702,704-718,760-761">
<font weight="400" style="normal" index="1">NotoSansCJK-Regular.ttc</font>
</family>
- <family lang="ja" pages="0,32,34-35,46-159,249-250,254-255,498,512-523,525-527,530-538,540-543,545-547,550,552,554-559,561,563-568,570,572-573,575-579,582-584,586-594,596-608,610-612,614-618,620,622-625,627-628,630-631,633-638,640,642-646,649-655,658,660-664,666,669-678,681,695-696,760-761">
- <font weight="400" style="normal">NotoSansJP-Regular.otf</font>
- </family>
<family pages="0,32-33,35-39,41,43,48,50,254,496-502,4068,4072">
<font weight="400" style="normal">NotoEmoji-Regular.ttf</font>
</family>
diff --git a/src/cobalt/content/fonts/font_files/NotoSansCJK-Bold.ttc b/src/cobalt/content/fonts/font_files/NotoSansCJK-Bold.ttc
deleted file mode 100644
index 09707f9..0000000
--- a/src/cobalt/content/fonts/font_files/NotoSansCJK-Bold.ttc
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/content/fonts/font_files/NotoSansJP-Regular.otf b/src/cobalt/content/fonts/font_files/NotoSansJP-Regular.otf
deleted file mode 100644
index 40b064a..0000000
--- a/src/cobalt/content/fonts/font_files/NotoSansJP-Regular.otf
+++ /dev/null
Binary files differ
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index 3ca1222..89dd4ff 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -66,6 +66,14 @@
DISALLOW_COPY_AND_ASSIGN(RelayLoadEvent);
};
+namespace {
+// Ensure that the timer resolution is at the lowest 20 microseconds in
+// order to mitigate potential Spectre-related attacks. This is following
+// Mozilla's lead as described here:
+// https://www.mozilla.org/en-US/security/advisories/mfsa2018-01/
+const int64_t kPerformanceTimerMinResolutionInMicroseconds = 20;
+} // namespace
+
Window::Window(int width, int height, cssom::CSSParser* css_parser,
Parser* dom_parser, loader::FetcherFactory* fetcher_factory,
render_tree::ResourceProvider** resource_provider,
@@ -106,7 +114,10 @@
media_source_registry, resource_provider, animated_image_tracker,
image_cache, reduced_image_cache_capacity_manager,
remote_typeface_cache, mesh_cache, dom_stat_tracker, language)),
- performance_(new Performance(new base::SystemMonotonicClock())),
+ performance_(new Performance(new base::MinimumResolutionClock(
+ new base::SystemMonotonicClock(),
+ base::TimeDelta::FromMicroseconds(
+ kPerformanceTimerMinResolutionInMicroseconds)))),
ALLOW_THIS_IN_INITIALIZER_LIST(document_(new Document(
html_element_context_.get(),
Document::Options(
diff --git a/src/cobalt/layout/layout.cc b/src/cobalt/layout/layout.cc
index 2331e2e..1a26b41 100644
--- a/src/cobalt/layout/layout.cc
+++ b/src/cobalt/layout/layout.cc
@@ -127,20 +127,11 @@
}
}
-scoped_refptr<render_tree::Node> Layout(
- const icu::Locale& locale, const scoped_refptr<dom::Document>& document,
- int dom_max_element_depth, UsedStyleProvider* used_style_provider,
+scoped_refptr<render_tree::Node> GenerateRenderTreeFromBoxTree(
+ UsedStyleProvider* used_style_provider,
LayoutStatTracker* layout_stat_tracker,
- icu::BreakIterator* line_break_iterator,
- icu::BreakIterator* character_break_iterator,
scoped_refptr<BlockLevelBlockContainerBox>* initial_containing_block) {
- TRACE_EVENT0("cobalt::layout", "Layout()");
- UpdateComputedStylesAndLayoutBoxTree(
- locale, document, dom_max_element_depth, used_style_provider,
- layout_stat_tracker, line_break_iterator, character_break_iterator,
- initial_containing_block);
-
- // Add to render tree.
+ TRACE_EVENT0("cobalt::layout", "GenerateRenderTreeFromBoxTree()");
render_tree::CompositionNode::Builder render_tree_root_builder;
{
TRACE_EVENT0("cobalt::layout", kBenchmarkStatRenderAndAnimate);
diff --git a/src/cobalt/layout/layout.h b/src/cobalt/layout/layout.h
index a777318..678fe54 100644
--- a/src/cobalt/layout/layout.h
+++ b/src/cobalt/layout/layout.h
@@ -38,7 +38,8 @@
// (https://www.w3.org/TR/CSS2/visuren.html) as recommended by a newer draft
// (http://dev.w3.org/csswg/css-box/) which is undergoing active changes.
-// Update the computed styles, then generate and layout the box tree.
+// Update the computed styles, then generate and layout the box tree produced
+// by the given document.
void UpdateComputedStylesAndLayoutBoxTree(
const icu::Locale& locale, const scoped_refptr<dom::Document>& document,
int dom_max_element_depth, UsedStyleProvider* used_style_provider,
@@ -47,15 +48,11 @@
icu::BreakIterator* character_break_iterator,
scoped_refptr<BlockLevelBlockContainerBox>* initial_containing_block);
-// Main entry point to the layout engine.
-// Produces the render tree (along with corresponding animations) which is a
-// result of recursive layout of the given HTML element.
-scoped_refptr<render_tree::Node> Layout(
- const icu::Locale& locale, const scoped_refptr<dom::Document>& document,
- int dom_max_element_depth, UsedStyleProvider* used_style_provider,
+// Generates the render tree (along with corresponding animations) of the box
+// tree contained within the provided containing block.
+scoped_refptr<render_tree::Node> GenerateRenderTreeFromBoxTree(
+ UsedStyleProvider* used_style_provider,
LayoutStatTracker* layout_stat_tracker,
- icu::BreakIterator* line_break_iterator,
- icu::BreakIterator* character_break_iterator,
scoped_refptr<BlockLevelBlockContainerBox>* initial_containing_block);
} // namespace layout
diff --git a/src/cobalt/layout/layout_manager.cc b/src/cobalt/layout/layout_manager.cc
index 0ada5d3..36ddfc8 100644
--- a/src/cobalt/layout/layout_manager.cc
+++ b/src/cobalt/layout/layout_manager.cc
@@ -57,9 +57,10 @@
void Suspend();
void Resume();
- bool IsNewRenderTreePending() const;
+ bool IsRenderTreePending() const;
private:
+ void DirtyLayout();
void StartLayoutTimer();
void DoLayoutAndProduceRenderTree();
@@ -73,11 +74,13 @@
const OnRenderTreeProducedCallback on_render_tree_produced_callback_;
const LayoutTrigger layout_trigger_;
- // This flag indicates whether or not we should do a re-layout. The flag
- // is checked at a regular interval (e.g. 60Hz) and if it is set to true,
- // a layout is initiated and it is set back to false. Events such as
- // DOM mutations will set this flag back to true.
- base::CVal<bool> layout_dirty_;
+ // Setting these flags triggers an update of the layout box tree and the
+ // generation of a new render tree at a regular interval (e.g. 60Hz). Events
+ // such as DOM mutations cause them to be set to true. While the render tree
+ // is excusively produced at the regular interval, the box tree can also be
+ // updated via a call to DoSynchronousLayout().
+ bool are_computed_styles_and_box_tree_dirty_;
+ base::CVal<bool> is_render_tree_pending_;
// Construction of |BreakIterator| requires a disk read, so we cache them
// in the layout manager in order to reuse them with all layouts happening
@@ -106,9 +109,7 @@
void UpdateCamera(
float width_to_height_aspect_ratio, scoped_refptr<dom::Camera3DImpl> camera,
float max_horizontal_fov_rad, float max_vertical_fov_rad,
- render_tree::MatrixTransform3DNode::Builder* transform_node_builder,
- base::TimeDelta time) {
- UNREFERENCED_PARAMETER(time);
+ render_tree::MatrixTransform3DNode::Builder* transform_node_builder) {
transform_node_builder->transform = camera->QueryViewPerspectiveMatrix(
width_to_height_aspect_ratio, max_horizontal_fov_rad,
max_vertical_fov_rad);
@@ -151,9 +152,10 @@
base::Bind(&AttachCameraNodes, window), enable_image_animations)),
on_render_tree_produced_callback_(on_render_tree_produced),
layout_trigger_(layout_trigger),
- layout_dirty_(StringPrintf("%s.Layout.IsDirty", name.c_str()), true,
- "Non-zero when the layout is dirty and a new render tree "
- "is pending."),
+ are_computed_styles_and_box_tree_dirty_(true),
+ is_render_tree_pending_(
+ StringPrintf("%s.Layout.IsRenderTreePending", name.c_str()), true,
+ "Non-zero when a new render tree is pending."),
layout_timer_(true, true, true),
dom_max_element_depth_(dom_max_element_depth),
layout_refresh_rate_(layout_refresh_rate),
@@ -197,7 +199,7 @@
#if defined(ENABLE_TEST_RUNNER)
if (layout_trigger_ == kTestRunnerMode &&
!window_->test_runner()->should_wait()) {
- layout_dirty_ = true;
+ DirtyLayout();
// Run the |DoLayoutAndProduceRenderTree| task after onload event finished.
MessageLoop::current()->PostTask(
@@ -210,7 +212,7 @@
void LayoutManager::Impl::OnMutation() {
if (layout_trigger_ == kOnDocumentMutation) {
- layout_dirty_ = true;
+ DirtyLayout();
}
}
@@ -220,11 +222,14 @@
return;
}
- layout::UpdateComputedStylesAndLayoutBoxTree(
- locale_, window_->document(), dom_max_element_depth_,
- used_style_provider_.get(), layout_stat_tracker_,
- line_break_iterator_.get(), character_break_iterator_.get(),
- &initial_containing_block_);
+ if (are_computed_styles_and_box_tree_dirty_) {
+ layout::UpdateComputedStylesAndLayoutBoxTree(
+ locale_, window_->document(), dom_max_element_depth_,
+ used_style_provider_.get(), layout_stat_tracker_,
+ line_break_iterator_.get(), character_break_iterator_.get(),
+ &initial_containing_block_);
+ are_computed_styles_and_box_tree_dirty_ = false;
+ }
}
void LayoutManager::Impl::Suspend() {
@@ -245,18 +250,18 @@
void LayoutManager::Impl::Resume() {
// Mark that we are no longer suspended and indicate that the layout is
// dirty since when Suspend() was called we invalidated our previous layout.
- layout_dirty_ = true;
+ DirtyLayout();
suspended_ = false;
}
-bool LayoutManager::Impl::IsNewRenderTreePending() const {
- return layout_dirty_;
+bool LayoutManager::Impl::IsRenderTreePending() const {
+ return is_render_tree_pending_;
}
#if defined(ENABLE_TEST_RUNNER)
void LayoutManager::Impl::DoTestRunnerLayoutCallback() {
DCHECK_EQ(kTestRunnerMode, layout_trigger_);
- layout_dirty_ = true;
+ DirtyLayout();
if (layout_trigger_ == kTestRunnerMode &&
window_->test_runner()->should_wait()) {
@@ -272,6 +277,11 @@
}
#endif // ENABLE_TEST_RUNNER
+void LayoutManager::Impl::DirtyLayout() {
+ are_computed_styles_and_box_tree_dirty_ = true;
+ is_render_tree_pending_ = true;
+}
+
void LayoutManager::Impl::StartLayoutTimer() {
// TODO: Eventually we would like to instead base our layouts off of a
// "refresh" signal generated by the rasterizer, instead of trying to
@@ -304,7 +314,7 @@
bool has_layout_processing_started = false;
if (window_->HasPendingAnimationFrameCallbacks()) {
- if (layout_dirty_) {
+ if (are_computed_styles_and_box_tree_dirty_) {
has_layout_processing_started = true;
TRACE_EVENT_BEGIN0("cobalt::layout", kBenchmarkStatLayout);
// Update our computed style before running animation callbacks, so that
@@ -321,7 +331,10 @@
window_->RunAnimationFrameCallbacks();
}
- if (layout_dirty_) {
+ // It should never be possible for for the computed styles and box tree to
+ // be dirty when a render tree is not pending.
+ DCHECK(is_render_tree_pending_ || !are_computed_styles_and_box_tree_dirty_);
+ if (is_render_tree_pending_) {
if (!has_layout_processing_started) {
// We want to catch the beginning of all layout processing. If it didn't
// begin before the call to RunAnimationFrameCallbacks(), then the flow
@@ -329,11 +342,19 @@
TRACE_EVENT_BEGIN0("cobalt::layout", kBenchmarkStatLayout);
}
- scoped_refptr<render_tree::Node> render_tree_root = layout::Layout(
- locale_, window_->document(), dom_max_element_depth_,
- used_style_provider_.get(), layout_stat_tracker_,
- line_break_iterator_.get(), character_break_iterator_.get(),
- &initial_containing_block_);
+ if (are_computed_styles_and_box_tree_dirty_) {
+ layout::UpdateComputedStylesAndLayoutBoxTree(
+ locale_, window_->document(), dom_max_element_depth_,
+ used_style_provider_.get(), layout_stat_tracker_,
+ line_break_iterator_.get(), character_break_iterator_.get(),
+ &initial_containing_block_);
+ are_computed_styles_and_box_tree_dirty_ = false;
+ }
+
+ scoped_refptr<render_tree::Node> render_tree_root =
+ layout::GenerateRenderTreeFromBoxTree(used_style_provider_.get(),
+ layout_stat_tracker_,
+ &initial_containing_block_);
bool run_on_render_tree_produced_callback = true;
#if defined(ENABLE_TEST_RUNNER)
if (layout_trigger_ == kTestRunnerMode &&
@@ -348,8 +369,7 @@
*document->timeline()->current_time())));
}
- layout_dirty_ = false;
-
+ is_render_tree_pending_ = false;
TRACE_EVENT_END0("cobalt::layout", kBenchmarkStatLayout);
}
}
@@ -368,8 +388,8 @@
void LayoutManager::Suspend() { impl_->Suspend(); }
void LayoutManager::Resume() { impl_->Resume(); }
-bool LayoutManager::IsNewRenderTreePending() const {
- return impl_->IsNewRenderTreePending();
+bool LayoutManager::IsRenderTreePending() const {
+ return impl_->IsRenderTreePending();
}
} // namespace layout
diff --git a/src/cobalt/layout/layout_manager.h b/src/cobalt/layout/layout_manager.h
index a2cde5c..799516b 100644
--- a/src/cobalt/layout/layout_manager.h
+++ b/src/cobalt/layout/layout_manager.h
@@ -71,7 +71,7 @@
void Suspend();
void Resume();
- bool IsNewRenderTreePending() const;
+ bool IsRenderTreePending() const;
private:
class Impl;
diff --git a/src/cobalt/layout/replaced_box.cc b/src/cobalt/layout/replaced_box.cc
index 4811118..6cfe7bc 100644
--- a/src/cobalt/layout/replaced_box.cc
+++ b/src/cobalt/layout/replaced_box.cc
@@ -231,9 +231,7 @@
}
void AnimateVideoImage(const ReplacedBox::ReplaceImageCB& replace_image_cb,
- ImageNode::Builder* image_node_builder,
- base::TimeDelta time) {
- UNREFERENCED_PARAMETER(time);
+ ImageNode::Builder* image_node_builder) {
DCHECK(!replace_image_cb.is_null());
DCHECK(image_node_builder);
@@ -250,14 +248,27 @@
void AnimateVideoWithLetterboxing(
const ReplacedBox::ReplaceImageCB& replace_image_cb,
math::SizeF destination_size,
- CompositionNode::Builder* composition_node_builder, base::TimeDelta time) {
- UNREFERENCED_PARAMETER(time);
-
+ CompositionNode::Builder* composition_node_builder) {
DCHECK(!replace_image_cb.is_null());
DCHECK(composition_node_builder);
scoped_refptr<render_tree::Image> image = replace_image_cb.Run();
+ // If the image hasn't changed, then no need to change anything else. The
+ // image should be the first child (see AddLetterboxedImageToRenderTree()).
+ if (!composition_node_builder->children().empty()) {
+ render_tree::ImageNode* existing_image_node =
+ base::polymorphic_downcast<render_tree::ImageNode*>(
+ composition_node_builder->GetChild(0)->get());
+ if (existing_image_node->data().source.get() == image.get()) {
+ return;
+ }
+ }
+
+ // Reset the composition node from whatever it was before; we will recreate
+ // it anew in each animation frame.
+ composition_node_builder->Reset();
+
// TODO: Detect better when the intrinsic video size is used for the
// node size, and trigger a re-layout from the media element when the size
// changes.
diff --git a/src/cobalt/layout_tests/layout_tests.cc b/src/cobalt/layout_tests/layout_tests.cc
index ec0ce07..26b09b7 100644
--- a/src/cobalt/layout_tests/layout_tests.cc
+++ b/src/cobalt/layout_tests/layout_tests.cc
@@ -90,7 +90,7 @@
scoped_refptr<render_tree::animations::AnimateNode> animate_node =
new render_tree::animations::AnimateNode(layout_results.render_tree);
scoped_refptr<render_tree::Node> animated_tree =
- animate_node->Apply(layout_results.layout_time).animated;
+ animate_node->Apply(layout_results.layout_time).animated->source();
bool results =
pixel_tester.TestTree(animated_tree, GetParam().base_file_path);
diff --git a/src/cobalt/loader/image/animated_webp_image.cc b/src/cobalt/loader/image/animated_webp_image.cc
index 123d945..6f12f2d 100644
--- a/src/cobalt/loader/image/animated_webp_image.cc
+++ b/src/cobalt/loader/image/animated_webp_image.cc
@@ -211,6 +211,10 @@
*image_pointer = static_image->image();
}
+void DecodeError(const std::string& error) {
+ LOG(ERROR) << error;
+}
+
} // namespace
bool AnimatedWebPImage::DecodeOneFrame(int frame_index) {
@@ -233,7 +237,7 @@
ImageDecoder image_decoder(
resource_provider_, base::Bind(&RecordImage, &next_frame_image),
- ImageDecoder::ErrorCallback(), ImageDecoder::kImageTypeWebP);
+ base::Bind(&DecodeError), ImageDecoder::kImageTypeWebP);
image_decoder.DecodeChunk(
reinterpret_cast<const char*>(webp_iterator.fragment.bytes),
webp_iterator.fragment.size);
diff --git a/src/cobalt/loader/image/image.h b/src/cobalt/loader/image/image.h
index 2b72010..740c9b5 100644
--- a/src/cobalt/loader/image/image.h
+++ b/src/cobalt/loader/image/image.h
@@ -140,10 +140,7 @@
scoped_refptr<FrameProvider> frame_provider,
const math::RectF& destination_rect,
const math::Matrix3F& local_transform,
- render_tree::ImageNode::Builder* image_node_builder,
- base::TimeDelta time) {
- UNREFERENCED_PARAMETER(time);
-
+ render_tree::ImageNode::Builder* image_node_builder) {
image_node_builder->source = frame_provider->GetFrame();
image_node_builder->destination_rect = destination_rect;
image_node_builder->local_transform = local_transform;
diff --git a/src/cobalt/loader/image/webp_image_decoder.cc b/src/cobalt/loader/image/webp_image_decoder.cc
index 5c803bc..f44986b 100644
--- a/src/cobalt/loader/image/webp_image_decoder.cc
+++ b/src/cobalt/loader/image/webp_image_decoder.cc
@@ -106,6 +106,7 @@
DLOG(ERROR) << "WebPIAppend error, status code: " << status;
DeleteInternalDecoder();
set_state(kError);
+ return 0;
}
} else {
animated_webp_image_->AppendChunk(data, input_byte);
diff --git a/src/cobalt/loader/resource_cache.h b/src/cobalt/loader/resource_cache.h
index 336d137..024cc55 100644
--- a/src/cobalt/loader/resource_cache.h
+++ b/src/cobalt/loader/resource_cache.h
@@ -510,10 +510,14 @@
base::ThreadChecker resource_cache_thread_checker_;
- base::CVal<base::cval::SizeInBytes, base::CValPublic> size_in_bytes_;
- base::CVal<base::cval::SizeInBytes, base::CValPublic> capacity_in_bytes_;
- base::CVal<int> count_requested_resources_;
- base::CVal<int> count_loading_resources_;
+ base::CVal<base::cval::SizeInBytes, base::CValPublic> memory_size_in_bytes_;
+ base::CVal<base::cval::SizeInBytes, base::CValPublic>
+ memory_capacity_in_bytes_;
+ base::CVal<base::cval::SizeInBytes> memory_resources_loaded_in_bytes_;
+
+ base::CVal<int> count_resources_requested_;
+ base::CVal<int> count_resources_loading_;
+ base::CVal<int> count_resources_loaded_;
base::CVal<int> count_pending_callbacks_;
DISALLOW_COPY_AND_ASSIGN(ResourceCache);
@@ -532,20 +536,28 @@
create_loader_function_(create_loader_function),
is_processing_pending_callbacks_(false),
are_callbacks_disabled_(false),
- size_in_bytes_(base::StringPrintf("Memory.%s.Size", name_.c_str()), 0,
- "Total number of bytes currently used by the cache."),
- capacity_in_bytes_(
+ memory_size_in_bytes_(
+ base::StringPrintf("Memory.%s.Size", name_.c_str()), 0,
+ "Total number of bytes currently used by the cache."),
+ memory_capacity_in_bytes_(
base::StringPrintf("Memory.%s.Capacity", name_.c_str()),
cache_capacity_,
"The capacity, in bytes, of the resource cache. "
"Exceeding this results in *unused* resources being "
"purged."),
- count_requested_resources_(
- base::StringPrintf("Count.%s.RequestedResources", name_.c_str()), 0,
+ memory_resources_loaded_in_bytes_(
+ base::StringPrintf("Memory.%s.Resource.Loaded", name_.c_str()), 0,
+ "Combined size in bytes of all resources that have been loaded by "
+ "the cache."),
+ count_resources_requested_(
+ base::StringPrintf("Count.%s.Resource.Requested", name_.c_str()), 0,
"The total number of resources that have been requested."),
- count_loading_resources_(
- base::StringPrintf("Count.%s.LoadingResources", name_.c_str()), 0,
- "The number of loading resources that are still outstanding."),
+ count_resources_loading_(
+ base::StringPrintf("Count.%s.Resource.Loading", name_.c_str()), 0,
+ "The number of resources that are currently loading."),
+ count_resources_loaded_(
+ base::StringPrintf("Count.%s.Resource.Loaded", name_.c_str()), 0,
+ "The total number of resources that have been successfully loaded."),
count_pending_callbacks_(
base::StringPrintf("Count.%s.PendingCallbacks", name_.c_str()), 0,
"The number of loading completed resources that have pending "
@@ -580,7 +592,7 @@
}
// If we reach this point, then the resource doesn't exist yet.
- ++count_requested_resources_;
+ ++count_resources_requested_;
// Add the resource to a loading set. If no current resources have pending
// callbacks, then this resource will block callbacks until it is decoded.
@@ -593,7 +605,7 @@
} else {
non_callback_blocking_loading_resource_set_.insert(url.spec());
}
- ++count_loading_resources_;
+ ++count_resources_loading_;
// Create the cached resource and fetch its resource based on the url.
scoped_refptr<CachedResourceType> cached_resource(new CachedResourceType(
@@ -613,7 +625,7 @@
void ResourceCache<CacheType>::SetCapacity(uint32 capacity) {
DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
cache_capacity_ = capacity;
- capacity_in_bytes_ = capacity;
+ memory_capacity_in_bytes_ = capacity;
ReclaimMemoryAndMaybeProcessPendingCallbacks(cache_capacity_);
}
@@ -637,8 +649,12 @@
const std::string& url = cached_resource->url().spec();
if (cached_resource->TryGetResource()) {
- size_in_bytes_ +=
+ uint32 estimated_size_in_bytes =
CacheType::GetEstimatedSizeInBytes(cached_resource->TryGetResource());
+ memory_size_in_bytes_ += estimated_size_in_bytes;
+ memory_resources_loaded_in_bytes_ += estimated_size_in_bytes;
+
+ ++count_resources_loaded_;
}
// Remove the resource from its loading set. It should exist in exactly one
@@ -659,7 +675,7 @@
// incremented first to ensure that the total of the two counts always remains
// above 0.
++count_pending_callbacks_;
- --count_loading_resources_;
+ --count_resources_loading_;
ProcessPendingCallbacksIfUnblocked();
ReclaimMemoryAndMaybeProcessPendingCallbacks(cache_capacity_);
@@ -690,10 +706,10 @@
DCHECK(non_callback_blocking_loading_resource_set_.find(url) ==
non_callback_blocking_loading_resource_set_.end());
DCHECK(pending_callback_map_.find(url) == pending_callback_map_.end());
- --count_loading_resources_;
+ --count_resources_loading_;
} else if (non_callback_blocking_loading_resource_set_.erase(url)) {
DCHECK(pending_callback_map_.find(url) == pending_callback_map_.end());
- --count_loading_resources_;
+ --count_resources_loading_;
} else if (pending_callback_map_.erase(url)) {
--count_pending_callbacks_;
}
@@ -717,7 +733,7 @@
// pending callbacks and try again. References to the cached resources are
// potentially being held until the callbacks run, so processing them may
// enable more memory to be reclaimed.
- if (size_in_bytes_ > bytes_to_reclaim_down_to) {
+ if (memory_size_in_bytes_ > bytes_to_reclaim_down_to) {
ProcessPendingCallbacks();
ReclaimMemory(bytes_to_reclaim_down_to, true /*log_warning_if_over*/);
}
@@ -728,7 +744,7 @@
bool log_warning_if_over) {
DCHECK(resource_cache_thread_checker_.CalledOnValidThread());
- while (size_in_bytes_ > bytes_to_reclaim_down_to &&
+ while (memory_size_in_bytes_ > bytes_to_reclaim_down_to &&
!unreference_cached_resource_map_.empty()) {
// The first element is the earliest-inserted element.
scoped_refptr<ResourceType> resource =
@@ -739,15 +755,15 @@
// in linked_hash_map. Add that function and related unit test.
unreference_cached_resource_map_.erase(
unreference_cached_resource_map_.begin());
- size_in_bytes_ -= first_resource_size;
+ memory_size_in_bytes_ -= first_resource_size;
}
if (log_warning_if_over) {
// Log a warning if we're still over |bytes_to_reclaim_down_to| after
// attempting to reclaim memory. This can occur validly when the size of
// the referenced images exceeds the target size.
- DLOG_IF(WARNING, size_in_bytes_ > bytes_to_reclaim_down_to)
- << "cached size: " << size_in_bytes_
+ DLOG_IF(WARNING, memory_size_in_bytes_ > bytes_to_reclaim_down_to)
+ << "cached size: " << memory_size_in_bytes_
<< ", target size: " << bytes_to_reclaim_down_to;
}
}
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index e783005..95f2b55 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -653,7 +653,7 @@
void SbPlayerPipeline::OnDemuxerSeeked(PipelineStatus status) {
DCHECK(message_loop_->BelongsToCurrentThread());
- if (status == PIPELINE_OK) {
+ if (status == PIPELINE_OK && player_) {
player_->Seek(seek_time_);
}
}
diff --git a/src/cobalt/render_tree/animations/animate_node.cc b/src/cobalt/render_tree/animations/animate_node.cc
index 58c11b4..dfdc7c1 100644
--- a/src/cobalt/render_tree/animations/animate_node.cc
+++ b/src/cobalt/render_tree/animations/animate_node.cc
@@ -66,7 +66,8 @@
TraverseList* traverse_list)
: animation_map_(animation_map),
traverse_list_(traverse_list),
- expiry_(-base::TimeDelta::Max()) {}
+ expiry_(-base::TimeDelta::Max()),
+ depends_on_time_expiry_(-base::TimeDelta::Max()) {}
void Visit(animations::AnimateNode* animate) OVERRIDE;
void Visit(CompositionNode* composition) OVERRIDE { VisitNode(composition); }
@@ -117,6 +118,10 @@
// The time after which all animations will have completed and be constant.
base::TimeDelta expiry_;
+ // Similar to |expiry_| but accumulated only for animations whose callback
+ // depends on the time parameter.
+ base::TimeDelta depends_on_time_expiry_;
+
friend class AnimateNode;
};
@@ -146,6 +151,9 @@
// Update our expiry in accordance with the sub-AnimateNode's expiry.
expiry_ = std::max(expiry_, animate->expiry());
+
+ depends_on_time_expiry_ =
+ std::max(depends_on_time_expiry_, animate->depends_on_time_expiry());
}
template <typename T>
@@ -208,8 +216,10 @@
void AnimateNode::TraverseListBuilder::AddToTraverseList(
Node* node, AnimateNode::Builder::InternalMap::const_iterator found) {
if (found != animation_map_.end()) {
- traverse_list_->push_back(TraverseListEntry(node, found->second));
+ traverse_list_->push_back(TraverseListEntry(node, found->second, false));
expiry_ = std::max(expiry_, found->second->GetExpiry());
+ depends_on_time_expiry_ = std::max(depends_on_time_expiry_,
+ found->second->GetDependsOnTimeExpiry());
} else {
traverse_list_->push_back(TraverseListEntry(node));
}
@@ -304,7 +314,9 @@
TraverseListEntry current_entry = AdvanceIterator(node);
DCHECK(current_entry.animations);
- ProcessAnimatedNodeBounds(current_entry, node);
+ if (current_entry.did_animate_previously) {
+ ProcessAnimatedNodeBounds(current_entry, node);
+ }
}
template <typename T>
@@ -336,7 +348,7 @@
}
transform_ = old_transform;
- if (current_entry.animations) {
+ if (current_entry.did_animate_previously) {
ProcessAnimatedNodeBounds(current_entry, node);
}
}
@@ -376,7 +388,8 @@
// tree.
class AnimateNode::ApplyVisitor : public NodeVisitor {
public:
- ApplyVisitor(const TraverseList& traverse_list, base::TimeDelta time_offset);
+ ApplyVisitor(const TraverseList& traverse_list, base::TimeDelta time_offset,
+ const base::optional<base::TimeDelta>& snapshot_time);
void Visit(animations::AnimateNode* /* animate */) OVERRIDE {
// An invariant of AnimateNodes is that they should never contain descendant
@@ -405,7 +418,9 @@
// As we compute the animated nodes, we create a new traverse list that leads
// to the newly created animated nodes. This can be used afterwards to
// calculate the bounding boxes around the active animated nodes.
- TraverseList* animated_traverse_list() { return &animated_traverse_list_; }
+ const TraverseList& animated_traverse_list() const {
+ return animated_traverse_list_;
+ }
private:
template <typename T>
@@ -415,8 +430,8 @@
typename base::enable_if<ChildIterator<T>::has_children>::type VisitNode(
T* node);
template <typename T>
- scoped_refptr<Node> ApplyAnimations(const TraverseListEntry& entry,
- typename T::Builder* builder);
+ scoped_refptr<T> ApplyAnimations(const TraverseListEntry& entry,
+ typename T::Builder* builder);
TraverseListEntry AdvanceIterator(Node* node);
// The time offset to be passed in to individual animations.
@@ -435,11 +450,18 @@
// An iterator pointing to the next valid render tree node to visit.
TraverseList::const_iterator iterator_;
+
+ // Time at which the existing source render tree was created/last animated
+ // at.
+ base::optional<base::TimeDelta> snapshot_time_;
};
-AnimateNode::ApplyVisitor::ApplyVisitor(const TraverseList& traverse_list,
- base::TimeDelta time_offset)
- : time_offset_(time_offset), traverse_list_(traverse_list) {
+AnimateNode::ApplyVisitor::ApplyVisitor(
+ const TraverseList& traverse_list, base::TimeDelta time_offset,
+ const base::optional<base::TimeDelta>& snapshot_time)
+ : time_offset_(time_offset),
+ traverse_list_(traverse_list),
+ snapshot_time_(snapshot_time) {
animated_traverse_list_.reserve(traverse_list.size());
iterator_ = traverse_list_.begin();
}
@@ -452,9 +474,18 @@
// have animations.
DCHECK(current_entry.animations);
typename T::Builder builder(node->data());
- animated_ = ApplyAnimations<T>(current_entry, &builder);
+ scoped_refptr<T> animated = ApplyAnimations<T>(current_entry, &builder);
+ // If nothing ends up getting animated, then just re-use the existing node.
+ bool did_animate = false;
+ if (animated->data() == node->data()) {
+ animated_ = node;
+ } else {
+ animated_ = animated.get();
+ did_animate = true;
+ }
+
animated_traverse_list_.push_back(
- TraverseListEntry(animated_, current_entry.animations));
+ TraverseListEntry(animated_, current_entry.animations, did_animate));
}
template <typename T>
@@ -464,7 +495,7 @@
size_t animated_traverse_list_index = animated_traverse_list_.size();
animated_traverse_list_.push_back(
- TraverseListEntry(NULL, current_entry.animations));
+ TraverseListEntry(NULL, current_entry.animations, false));
// Traverse the child nodes, but only the ones that are on the
// |traverse_list_|. In particular, the next node we are allowed to visit
@@ -482,41 +513,60 @@
// If one of our children is next up on the path to animation, traverse
// into it.
child->Accept(this);
- // Traversing into the child means that it was animated, and so replaced
- // by an animated node while it was visited. Thus, replace it in the
- // current node's child list with its animated version.
- child_iterator.ReplaceCurrent(animated_);
- children_modified = true;
+ if (animated_ != child) {
+ // Traversing into the child and seeing |animated_| emerge from the
+ // traversal equal to something other than |child| means that the child
+ // was animated, and so replaced by an animated node while it was
+ // visited. Thus, replace it in the current node's child list with its
+ // animated version.
+ child_iterator.ReplaceCurrent(animated_);
+ children_modified = true;
+ }
}
child_iterator.Next();
}
+ base::optional<typename T::Builder> builder;
+ if (children_modified) {
+ // Reuse the modified Builder object from child traversal if one of
+ // our children was animated.
+ builder.emplace(child_iterator.TakeReplacedChildrenBuilder());
+ }
+
+ bool did_animate = false;
if (current_entry.animations) {
- base::optional<typename T::Builder> builder;
- if (children_modified) {
- // Reuse the modified Builder object from child traversal if one of
- // our children was animated.
- builder.emplace(child_iterator.TakeReplacedChildrenBuilder());
- } else {
+ if (!builder) {
// Create a fresh copy of the Builder object for this animated node, to
// be passed into the animations.
builder.emplace(node->data());
}
- animated_ = ApplyAnimations<T>(current_entry, &(*builder));
+ typename T::Builder original_builder(*builder);
+ scoped_refptr<T> animated = ApplyAnimations<T>(current_entry, &(*builder));
+ if (!(original_builder == *builder)) {
+ did_animate = true;
+ }
+ // If the data didn't actually change, then no animation took place and
+ // so we should note this by not modifying the original render tree node.
+ animated_ = animated->data() == node->data() ? node : animated.get();
} else {
- // If there were no animations targeting this node directly, then its
- // children must have been modified since otherwise it wouldn't be in
- // the traverse list.
- DCHECK(children_modified);
- animated_ = new T(child_iterator.TakeReplacedChildrenBuilder());
+ // If there were no animations targeting this node directly, it may still
+ // need to be animated if its children are animated, which will be the
+ // case if |builder| is populated.
+ if (builder) {
+ animated_ = new T(*builder);
+ } else {
+ animated_ = node;
+ }
}
animated_traverse_list_[animated_traverse_list_index].node = animated_;
+ animated_traverse_list_[animated_traverse_list_index].did_animate_previously =
+ did_animate;
}
template <typename T>
-scoped_refptr<Node> AnimateNode::ApplyVisitor::ApplyAnimations(
+scoped_refptr<T> AnimateNode::ApplyVisitor::ApplyAnimations(
const TraverseListEntry& entry, typename T::Builder* builder) {
TRACE_EVENT0("cobalt::renderer",
"AnimateNode::ApplyVisitor::ApplyAnimations()");
@@ -525,11 +575,16 @@
base::polymorphic_downcast<const AnimationList<T>*>(
entry.animations.get());
- // Iterate through each animation applying them one at a time.
- for (typename AnimationList<T>::InternalList::const_iterator iter =
- typed_node_animations->data().animations.begin();
- iter != typed_node_animations->data().animations.end(); ++iter) {
- iter->Run(builder, time_offset_);
+ // Only execute the animation updates on nodes that have not expired.
+ if (!snapshot_time_ ||
+ typed_node_animations->data().expiry >= *snapshot_time_) {
+ TRACE_EVENT0("cobalt::renderer", "Running animation callbacks");
+ // Iterate through each animation applying them one at a time.
+ for (typename AnimationList<T>::InternalList::const_iterator iter =
+ typed_node_animations->data().animations.begin();
+ iter != typed_node_animations->data().animations.end(); ++iter) {
+ iter->Run(builder, time_offset_);
+ }
}
return new T(*builder);
@@ -559,9 +614,9 @@
class AnimateNode::RefCountedTraversalList
: public base::RefCounted<RefCountedTraversalList> {
public:
- explicit RefCountedTraversalList(TraverseList* to_swap_in) {
- traverse_list_.swap(*to_swap_in);
- }
+ explicit RefCountedTraversalList(const TraverseList& traverse_list)
+ : traverse_list_(traverse_list) {}
+
const TraverseList& traverse_list() const { return traverse_list_; }
private:
@@ -594,24 +649,41 @@
AnimateNode::AnimateResults AnimateNode::Apply(base::TimeDelta time_offset) {
TRACE_EVENT0("cobalt::renderer", "AnimateNode::Apply()");
+ if (snapshot_time_) {
+ // Assume we are always animating forward.
+ DCHECK_LE(*snapshot_time_, time_offset);
+ }
+
AnimateResults results;
if (traverse_list_.empty()) {
- results.animated = source_;
+ results.animated = this;
// There are no animations, so there is no bounding rectangle, so setup the
// bounding box function to trivially return an empty rectangle.
results.get_animation_bounds_since =
base::Bind(&ReturnTrivialEmptyRectBound);
} else {
- ApplyVisitor apply_visitor(traverse_list_, time_offset);
+ ApplyVisitor apply_visitor(traverse_list_, time_offset, snapshot_time_);
source_->Accept(&apply_visitor);
- results.animated = apply_visitor.animated();
- // Setup a function for returning
+ // Setup a function for returning the bounds on the regions modified by
+ // animations given a specific starting point in time ("since"). This
+ // can be used by rasterizers to determine which regions need to be
+ // re-drawn or not.
results.get_animation_bounds_since = base::Bind(
&GetAnimationBoundsSince,
scoped_refptr<RefCountedTraversalList>(new RefCountedTraversalList(
apply_visitor.animated_traverse_list())),
- time_offset, results.animated);
+ time_offset, apply_visitor.animated());
+
+ if (apply_visitor.animated() == source()) {
+ // If no animations were actually applied, indicate this by returning
+ // this exact node as the animated node.
+ results.animated = this;
+ } else {
+ results.animated = new AnimateNode(apply_visitor.animated_traverse_list(),
+ apply_visitor.animated(), expiry_,
+ depends_on_time_expiry_, time_offset);
+ }
}
return results;
}
@@ -638,6 +710,7 @@
}
expiry_ = traverse_list_builder.expiry_;
+ depends_on_time_expiry_ = traverse_list_builder.depends_on_time_expiry_;
}
} // namespace animations
diff --git a/src/cobalt/render_tree/animations/animate_node.h b/src/cobalt/render_tree/animations/animate_node.h
index 70ed9ba..c71e620 100644
--- a/src/cobalt/render_tree/animations/animate_node.h
+++ b/src/cobalt/render_tree/animations/animate_node.h
@@ -20,6 +20,7 @@
#include "base/containers/small_map.h"
#include "base/memory/ref_counted.h"
+#include "base/optional.h"
#include "cobalt/math/rect_f.h"
#include "cobalt/render_tree/animations/animation_list.h"
#include "cobalt/render_tree/movable.h"
@@ -80,12 +81,30 @@
}
template <typename T>
+ void Add(
+ const scoped_refptr<T>& target_node,
+ const typename Animation<T>::TimeIndependentFunction& single_animation,
+ base::TimeDelta expiry) {
+ AddInternal(target_node,
+ scoped_refptr<AnimationListBase>(
+ new AnimationList<T>(single_animation, expiry)));
+ }
+
+ template <typename T>
void Add(const scoped_refptr<T>& target_node,
const typename Animation<T>::Function& single_animation) {
AddInternal(target_node,
scoped_refptr<AnimationListBase>(new AnimationList<T>(
single_animation, base::TimeDelta::Max())));
}
+ template <typename T>
+ void Add(const scoped_refptr<T>& target_node,
+ const typename Animation<T>::TimeIndependentFunction&
+ single_animation) {
+ AddInternal(target_node,
+ scoped_refptr<AnimationListBase>(new AnimationList<T>(
+ single_animation, base::TimeDelta::Max())));
+ }
// Merge all mappings from another AnimateNode::Builder into this one.
// There cannot be any keys that are in both the merge target and source.
@@ -140,8 +159,11 @@
struct AnimateResults {
// The animated render tree, which is guaranteed to not contain any
- // AnimateNodes.
- scoped_refptr<Node> animated;
+ // AnimateNodes. Note that it is returned as an AnimateNode... The actual
+ // animated render tree can be obtained via |animated->source()|, the
+ // AnimateNode however allows one to animate the animated node so that
+ // it can be checked if anything has actually been animated or not.
+ scoped_refptr<AnimateNode> animated;
// Can be called in order to return a bounding rectangle around all
// nodes that are actively animated. The parameter specifies a "since"
@@ -152,6 +174,9 @@
base::Callback<math::RectF(base::TimeDelta)> get_animation_bounds_since;
};
// Apply the animations to the sub render tree with the given |time_offset|.
+ // Note that if |animated| in the results is equivalent to |this| on which
+ // Apply() is called, then no animations have actually taken place, and a
+ // re-render can be avoided.
AnimateResults Apply(base::TimeDelta time_offset);
// Returns the sub-tree for which the animations apply to.
@@ -163,6 +188,12 @@
// all x, y >= expiry().
const base::TimeDelta& expiry() const { return expiry_; }
+ // Similar to |expiry()|, but returns the expiration for all animations whose
+ // callback function actually depends on the time parameter passed into them.
+ const base::TimeDelta& depends_on_time_expiry() const {
+ return depends_on_time_expiry_;
+ }
+
private:
// The compiled node animation list is a sequence of nodes that are either
// animated themselves, or on the path to an animated node. Only nodes in
@@ -170,12 +201,19 @@
// list, complete with animations to be applied to the given node, if any.
struct TraverseListEntry {
TraverseListEntry(Node* node,
- const scoped_refptr<AnimationListBase>& animations)
- : node(node), animations(animations) {}
- explicit TraverseListEntry(Node* node) : node(node) {}
+ const scoped_refptr<AnimationListBase>& animations,
+ bool did_animate_previously)
+ : node(node),
+ animations(animations),
+ did_animate_previously(did_animate_previously) {}
+ explicit TraverseListEntry(Node* node)
+ : node(node), did_animate_previously(false) {}
Node* node;
scoped_refptr<AnimationListBase> animations;
+ // Used when checking animation bounds to see which nodes were actually
+ // animated and whose bounds we need to accumulate.
+ bool did_animate_previously;
};
typedef std::vector<TraverseListEntry> TraverseList;
@@ -192,6 +230,18 @@
// rectangle for all active animations.
class BoundsVisitor;
+ // This private constructor is used by AnimateNode::Apply() to create a new
+ // AnimateNode that matches the resulting animated render tree.
+ AnimateNode(const TraverseList& traverse_list, scoped_refptr<Node> source,
+ const base::TimeDelta& expiry,
+ const base::TimeDelta& depends_on_time_expiry,
+ const base::TimeDelta& snapshot_time)
+ : traverse_list_(traverse_list),
+ source_(source),
+ expiry_(expiry),
+ depends_on_time_expiry_(depends_on_time_expiry),
+ snapshot_time_(snapshot_time) {}
+
void CommonInit(const Builder::InternalMap& node_animation_map,
const scoped_refptr<Node>& source);
@@ -205,6 +255,12 @@
TraverseList traverse_list_;
scoped_refptr<Node> source_;
base::TimeDelta expiry_;
+ base::TimeDelta depends_on_time_expiry_;
+ // Time when the |source_| render tree was last animated, if known. This
+ // will get set when Apply() is called to produce a new AnimateNode that can
+ // then be Apply()d again. It can be used during the second apply to check
+ // if some animations have expired.
+ base::optional<base::TimeDelta> snapshot_time_;
};
} // namespace animations
diff --git a/src/cobalt/render_tree/animations/animate_node_test.cc b/src/cobalt/render_tree/animations/animate_node_test.cc
index 7f539b9..b19733e 100644
--- a/src/cobalt/render_tree/animations/animate_node_test.cc
+++ b/src/cobalt/render_tree/animations/animate_node_test.cc
@@ -81,10 +81,10 @@
scoped_refptr<AnimateNode> with_animations =
CreateSingleAnimation(text_node, base::Bind(&AnimateText));
- scoped_refptr<Node> animated_render_tree =
+ scoped_refptr<AnimateNode> animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
TextNode* animated_text_node =
- dynamic_cast<TextNode*>(animated_render_tree.get());
+ dynamic_cast<TextNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_text_node);
EXPECT_EQ(ColorRGBA(.5f, .5f, .5f), animated_text_node->data().color);
}
@@ -101,10 +101,10 @@
scoped_refptr<AnimateNode> with_animations =
CreateSingleAnimation(image_node, base::Bind(&AnimateImage));
- scoped_refptr<Node> animated_render_tree =
+ scoped_refptr<AnimateNode> animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
ImageNode* animated_image_node =
- dynamic_cast<ImageNode*>(animated_render_tree.get());
+ dynamic_cast<ImageNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_image_node);
EXPECT_TRUE(animated_image_node);
EXPECT_EQ(RectF(2.0f, 2.0f), animated_image_node->data().destination_rect);
@@ -122,10 +122,10 @@
scoped_refptr<AnimateNode> with_animations =
CreateSingleAnimation(rect_node, base::Bind(&AnimateRect));
- scoped_refptr<Node> animated_render_tree =
+ scoped_refptr<AnimateNode> animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
RectNode* animated_rect_node =
- dynamic_cast<RectNode*>(animated_render_tree.get());
+ dynamic_cast<RectNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_rect_node);
EXPECT_TRUE(animated_rect_node);
EXPECT_EQ(RectF(2.0f, 2.0f), animated_rect_node->data().rect);
@@ -147,26 +147,29 @@
scoped_refptr<AnimateNode> with_animations =
CreateSingleAnimation(image_node, base::Bind(&AnimateImageAddWithTime));
- scoped_refptr<Node> animated_render_tree;
+ scoped_refptr<AnimateNode> animated_render_tree;
ImageNode* animated_image_node;
animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
- animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get());
+ animated_image_node =
+ dynamic_cast<ImageNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_image_node);
EXPECT_TRUE(animated_image_node);
EXPECT_FLOAT_EQ(3.0f, animated_image_node->data().destination_rect.width());
animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(2)).animated;
- animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get());
+ animated_image_node =
+ dynamic_cast<ImageNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_image_node);
EXPECT_TRUE(animated_image_node);
EXPECT_FLOAT_EQ(5.0f, animated_image_node->data().destination_rect.width());
animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(4)).animated;
- animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get());
+ animated_image_node =
+ dynamic_cast<ImageNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_image_node);
EXPECT_TRUE(animated_image_node);
EXPECT_FLOAT_EQ(9.0f, animated_image_node->data().destination_rect.width());
@@ -202,11 +205,11 @@
scoped_refptr<AnimateNode> with_animations(
new AnimateNode(animations_builder, image_node));
- scoped_refptr<Node> animated_render_tree =
+ scoped_refptr<AnimateNode> animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(3)).animated;
ImageNode* animated_image_node =
- dynamic_cast<ImageNode*>(animated_render_tree.get());
+ dynamic_cast<ImageNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_image_node);
EXPECT_FLOAT_EQ(21.0f, animated_image_node->data().destination_rect.width());
@@ -228,11 +231,11 @@
scoped_refptr<AnimateNode> with_animations =
CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform));
- scoped_refptr<Node> animated_render_tree =
+ scoped_refptr<AnimateNode> animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
MatrixTransformNode* animated_transform_node =
- dynamic_cast<MatrixTransformNode*>(animated_render_tree.get());
+ dynamic_cast<MatrixTransformNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_transform_node);
EXPECT_EQ(math::TranslateMatrix(2.0f, 2.0f),
@@ -260,11 +263,11 @@
scoped_refptr<AnimateNode> with_animations =
new AnimateNode(animation_builder, composition_node);
- scoped_refptr<Node> animated_render_tree =
+ scoped_refptr<AnimateNode> animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
CompositionNode* animated_composition_node =
- dynamic_cast<CompositionNode*>(animated_render_tree.get());
+ dynamic_cast<CompositionNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_composition_node);
ImageNode* animated_image_node_a = dynamic_cast<ImageNode*>(
@@ -291,11 +294,11 @@
scoped_refptr<AnimateNode> with_animations =
CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform));
- scoped_refptr<Node> animated_render_tree =
+ scoped_refptr<AnimateNode> animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
MatrixTransformNode* animated_transform_node =
- dynamic_cast<MatrixTransformNode*>(animated_render_tree.get());
+ dynamic_cast<MatrixTransformNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_transform_node);
// Check that the matrix transform node is animated.
@@ -328,11 +331,11 @@
scoped_refptr<AnimateNode> with_animations =
new AnimateNode(transform_node_b);
- scoped_refptr<Node> animated_render_tree =
+ scoped_refptr<AnimateNode> animated_render_tree =
with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated;
MatrixTransformNode* animated_transform_node =
- dynamic_cast<MatrixTransformNode*>(animated_render_tree.get());
+ dynamic_cast<MatrixTransformNode*>(animated_render_tree->source().get());
EXPECT_TRUE(animated_transform_node);
// Check that the image node is animated.
diff --git a/src/cobalt/render_tree/animations/animation_list.h b/src/cobalt/render_tree/animations/animation_list.h
index 78f1e65..b51144d 100644
--- a/src/cobalt/render_tree/animations/animation_list.h
+++ b/src/cobalt/render_tree/animations/animation_list.h
@@ -17,6 +17,7 @@
#include <list>
+#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/time.h"
@@ -68,6 +69,16 @@
// and ultimately add that to a AnimateNode::Builder object so that it can be
// mapped to a specific TextNode that it should be applied to.
typedef base::Callback<void(typename T::Builder*, base::TimeDelta)> Function;
+ typedef base::Callback<void(typename T::Builder*)> TimeIndependentFunction;
+
+ // Helper function to convert from time independent function to a time
+ // dependent function.
+ static void CallTimeIndependentFunction(
+ const TimeIndependentFunction& time_independent_function,
+ typename T::Builder* builder, base::TimeDelta time) {
+ UNREFERENCED_PARAMETER(time);
+ time_independent_function.Run(builder);
+ }
};
// The AnimationListBase is used so that we can acquire a non-template handle
@@ -77,6 +88,7 @@
class AnimationListBase : public base::RefCountedThreadSafe<AnimationListBase> {
public:
virtual base::TimeDelta GetExpiry() const = 0;
+ virtual base::TimeDelta GetDependsOnTimeExpiry() const = 0;
protected:
virtual ~AnimationListBase() {}
@@ -100,18 +112,36 @@
struct Builder {
DECLARE_AS_MOVABLE(Builder);
- Builder() : expiry(base::TimeDelta::Max()) {}
+ Builder() : expiry(base::TimeDelta::Max()),
+ depends_on_time_expiry(base::TimeDelta::Max()) {}
explicit Builder(Moved moved) { animations.swap(moved->animations); }
explicit Builder(const typename Animation<T>::Function& single_animation,
base::TimeDelta expiry)
- : expiry(expiry) {
+ : expiry(expiry), depends_on_time_expiry(expiry) {
animations.push_back(single_animation);
}
+ explicit Builder(
+ const typename Animation<T>::TimeIndependentFunction& single_animation,
+ base::TimeDelta expiry)
+ : expiry(expiry),
+ // Since this animation is time independent, we mark its
+ // time-dependent expiration to be already expired.
+ depends_on_time_expiry(-base::TimeDelta::Max()) {
+ // Transform from a time independent function to a time dependent
+ // function, since we store all functions as time dependent functions.
+ animations.push_back(base::Bind(Animation<T>::CallTimeIndependentFunction,
+ single_animation));
+ }
InternalList animations;
// When do the animations expire? base::TimeDelta::Max() implies that they
// never expire.
base::TimeDelta expiry;
+
+ // Similar to |expiry|, but only set for animations that depend on the
+ // time parameter passed into their animation callback functions.
+ // Optimizations can be performed for animations that don't depend on this.
+ base::TimeDelta depends_on_time_expiry;
};
explicit AnimationList(typename Builder::Moved builder) : data_(builder) {}
@@ -122,10 +152,17 @@
const typename Animation<T>::Function& single_animation,
base::TimeDelta expiry)
: data_(single_animation, expiry) {}
+ explicit AnimationList(
+ const typename Animation<T>::TimeIndependentFunction& single_animation,
+ base::TimeDelta expiry)
+ : data_(single_animation, expiry) {}
const Builder& data() const { return data_; }
base::TimeDelta GetExpiry() const OVERRIDE { return data_.expiry; }
+ base::TimeDelta GetDependsOnTimeExpiry() const OVERRIDE {
+ return data_.depends_on_time_expiry;
+ }
private:
~AnimationList() OVERRIDE {}
diff --git a/src/cobalt/render_tree/blur_filter.h b/src/cobalt/render_tree/blur_filter.h
index 5f6fbe7..cbe95a0 100644
--- a/src/cobalt/render_tree/blur_filter.h
+++ b/src/cobalt/render_tree/blur_filter.h
@@ -29,6 +29,10 @@
// |blur_sigma| must be non-negative.
explicit BlurFilter(float blur_sigma) { set_blur_sigma(blur_sigma); }
+ bool operator==(const BlurFilter& other) const {
+ return blur_sigma_ == other.blur_sigma_;
+ }
+
void set_blur_sigma(float blur_sigma) {
DCHECK_LE(0.0f, blur_sigma);
blur_sigma_ = blur_sigma;
diff --git a/src/cobalt/render_tree/border.h b/src/cobalt/render_tree/border.h
index 761c99f..9b72706 100644
--- a/src/cobalt/render_tree/border.h
+++ b/src/cobalt/render_tree/border.h
@@ -53,6 +53,11 @@
Border(const BorderSide& left, const BorderSide& right, const BorderSide& top,
const BorderSide& bottom);
+ bool operator==(const Border& other) const {
+ return left == other.left && right == other.right && top == other.top &&
+ bottom == other.bottom;
+ }
+
BorderSide left;
BorderSide right;
BorderSide top;
diff --git a/src/cobalt/render_tree/brush.h b/src/cobalt/render_tree/brush.h
index fba8063..988e470 100644
--- a/src/cobalt/render_tree/brush.h
+++ b/src/cobalt/render_tree/brush.h
@@ -45,6 +45,10 @@
public:
explicit SolidColorBrush(const ColorRGBA& color) : color_(color) {}
+ bool operator==(const SolidColorBrush& other) const {
+ return color_ == other.color_;
+ }
+
// A type-safe branching.
void Accept(BrushVisitor* visitor) const OVERRIDE;
@@ -68,6 +72,10 @@
ColorStop(float position, const ColorRGBA& color)
: position(position), color(color) {}
+ bool operator==(const ColorStop& other) const {
+ return position == other.position && color == other.color;
+ }
+
float position;
ColorRGBA color;
};
@@ -102,12 +110,20 @@
const ColorRGBA& source_color,
const ColorRGBA& dest_color);
+ bool operator==(const LinearGradientBrush& other) const {
+ return data_ == other.data_;
+ }
+
struct Data {
Data();
Data(const math::PointF& source, const math::PointF& dest,
const ColorStopList& color_stops);
Data(const math::PointF& source, const math::PointF& dest);
+ bool operator==(const Data& other) const {
+ return source_ == other.source_ && dest_ == other.dest_ &&
+ color_stops_ == other.color_stops_;
+ }
math::PointF source_;
math::PointF dest_;
@@ -167,6 +183,11 @@
float radius_y, const ColorRGBA& source_color,
const ColorRGBA& dest_color);
+ bool operator==(const RadialGradientBrush& other) const {
+ return center_ == other.center_ && radius_x_ == other.radius_x_ &&
+ radius_y_ == other.radius_y_ && color_stops_ == other.color_stops_;
+ }
+
// A type-safe branching.
void Accept(BrushVisitor* visitor) const OVERRIDE;
@@ -192,6 +213,36 @@
scoped_ptr<Brush> CloneBrush(const Brush* brush);
+class EqualsBrushVisitor : public BrushVisitor {
+ public:
+ explicit EqualsBrushVisitor(Brush* brush_a)
+ : brush_a_(brush_a), result_(false) {}
+
+ bool result() const { return result_; }
+
+ void Visit(const SolidColorBrush* solid_color_brush) OVERRIDE {
+ result_ =
+ brush_a_->GetTypeId() == base::GetTypeId<SolidColorBrush>() &&
+ *static_cast<const SolidColorBrush*>(brush_a_) == *solid_color_brush;
+ }
+
+ void Visit(const LinearGradientBrush* linear_gradient_brush) OVERRIDE {
+ result_ = brush_a_->GetTypeId() == base::GetTypeId<LinearGradientBrush>() &&
+ *static_cast<const LinearGradientBrush*>(brush_a_) ==
+ *linear_gradient_brush;
+ }
+
+ void Visit(const RadialGradientBrush* radial_gradient_brush) OVERRIDE {
+ result_ = brush_a_->GetTypeId() == base::GetTypeId<RadialGradientBrush>() &&
+ *static_cast<const RadialGradientBrush*>(brush_a_) ==
+ *radial_gradient_brush;
+ }
+
+ private:
+ Brush* brush_a_;
+ bool result_;
+};
+
} // namespace render_tree
} // namespace cobalt
diff --git a/src/cobalt/render_tree/composition_node.h b/src/cobalt/render_tree/composition_node.h
index 2614822..01d1296 100644
--- a/src/cobalt/render_tree/composition_node.h
+++ b/src/cobalt/render_tree/composition_node.h
@@ -72,6 +72,10 @@
children_.swap(moved->children_);
}
+ bool operator==(const Builder& other) const {
+ return offset_ == other.offset_ && children_ == other.children_;
+ }
+
// Add a child node to the list of children we are building. When a
// CompositionNode is constructed from this CompositionNode::Builder, it
// will have as children all nodes who were passed into the builder via this
@@ -94,6 +98,11 @@
const math::Vector2dF& offset() const { return offset_; }
void set_offset(const math::Vector2dF& offset) { offset_ = offset; }
+ void Reset() {
+ offset_ = math::Vector2dF();
+ ClearChildren();
+ }
+
private:
// A translation offset to be applied to each member of this composition
// node.
diff --git a/src/cobalt/render_tree/filter_node.h b/src/cobalt/render_tree/filter_node.h
index 5ba8ac7..820f31e 100644
--- a/src/cobalt/render_tree/filter_node.h
+++ b/src/cobalt/render_tree/filter_node.h
@@ -51,6 +51,13 @@
math::RectF GetBounds() const;
+ bool operator==(const Builder& other) const {
+ return source == other.source && opacity_filter == other.opacity_filter &&
+ viewport_filter == other.viewport_filter &&
+ blur_filter == other.blur_filter &&
+ map_to_mesh_filter == other.map_to_mesh_filter;
+ }
+
// The source tree, which will be used as the input to the filters specified
// in this FilterNode.
scoped_refptr<render_tree::Node> source;
diff --git a/src/cobalt/render_tree/image_node.h b/src/cobalt/render_tree/image_node.h
index ccd4773..7b5f048 100644
--- a/src/cobalt/render_tree/image_node.h
+++ b/src/cobalt/render_tree/image_node.h
@@ -36,6 +36,12 @@
const math::RectF& destination_rect,
const math::Matrix3F& local_transform);
+ bool operator==(const Builder& other) const {
+ return source == other.source &&
+ destination_rect == other.destination_rect &&
+ local_transform == other.local_transform;
+ }
+
// A source of pixels. May be smaller or larger than layed out image.
// The class does not own the image, it merely refers it from a resource
// pool.
diff --git a/src/cobalt/render_tree/map_to_mesh_filter.h b/src/cobalt/render_tree/map_to_mesh_filter.h
index 2c126e6..bbe4587 100644
--- a/src/cobalt/render_tree/map_to_mesh_filter.h
+++ b/src/cobalt/render_tree/map_to_mesh_filter.h
@@ -47,6 +47,12 @@
public:
struct Builder {
Builder() {}
+ bool operator==(const Builder& other) const {
+ return resolution_matched_meshes_ == other.resolution_matched_meshes_ &&
+ left_eye_default_mesh_ == other.left_eye_default_mesh_ &&
+ right_eye_default_mesh_ == other.right_eye_default_mesh_;
+ }
+
void SetDefaultMeshes(
const scoped_refptr<render_tree::Mesh>& left_eye_mesh,
const scoped_refptr<render_tree::Mesh>& right_eye_mesh) {
@@ -106,6 +112,10 @@
}
}
+ bool operator==(const MapToMeshFilter& other) const {
+ return stereo_mode_ == other.stereo_mode_ && data_ == other.data_;
+ }
+
StereoMode stereo_mode() const { return stereo_mode_; }
// The omission of the |resolution| parameter will yield the default
diff --git a/src/cobalt/render_tree/matrix_transform_3d_node.h b/src/cobalt/render_tree/matrix_transform_3d_node.h
index bbb517a..3732b68 100644
--- a/src/cobalt/render_tree/matrix_transform_3d_node.h
+++ b/src/cobalt/render_tree/matrix_transform_3d_node.h
@@ -37,6 +37,10 @@
Builder(const scoped_refptr<Node>& source, const glm::mat4& transform)
: source(source), transform(transform) {}
+ bool operator==(const Builder& other) const {
+ return source == other.source && transform == other.transform;
+ }
+
// The subtree that will be rendered with |transform| applied to it.
scoped_refptr<Node> source;
diff --git a/src/cobalt/render_tree/matrix_transform_node.h b/src/cobalt/render_tree/matrix_transform_node.h
index 3baafcc..93d9685 100644
--- a/src/cobalt/render_tree/matrix_transform_node.h
+++ b/src/cobalt/render_tree/matrix_transform_node.h
@@ -34,6 +34,10 @@
Builder(const scoped_refptr<Node>& source, const math::Matrix3F& transform)
: source(source), transform(transform) {}
+ bool operator==(const Builder& other) const {
+ return source == other.source && transform == other.transform;
+ }
+
// The subtree that will be rendered with |transform| applied to it.
scoped_refptr<Node> source;
diff --git a/src/cobalt/render_tree/opacity_filter.h b/src/cobalt/render_tree/opacity_filter.h
index 93e8718..113c909 100644
--- a/src/cobalt/render_tree/opacity_filter.h
+++ b/src/cobalt/render_tree/opacity_filter.h
@@ -27,6 +27,10 @@
public:
explicit OpacityFilter(float opacity) { set_opacity(opacity); }
+ bool operator==(const OpacityFilter& other) const {
+ return opacity_ == other.opacity_;
+ }
+
void set_opacity(float opacity) {
DCHECK_LE(0.0f, opacity);
DCHECK_GE(1.0f, opacity);
diff --git a/src/cobalt/render_tree/punch_through_video_node.h b/src/cobalt/render_tree/punch_through_video_node.h
index 3683d6a..f96da9a 100644
--- a/src/cobalt/render_tree/punch_through_video_node.h
+++ b/src/cobalt/render_tree/punch_through_video_node.h
@@ -45,6 +45,10 @@
Builder(const math::RectF& rect, const SetBoundsCB& set_bounds_cb)
: rect(rect), set_bounds_cb(set_bounds_cb) {}
+ bool operator==(const Builder& other) const {
+ return rect == other.rect && set_bounds_cb.Equals(other.set_bounds_cb);
+ }
+
// The destination rectangle (size includes border).
math::RectF rect;
const SetBoundsCB set_bounds_cb;
diff --git a/src/cobalt/render_tree/rect_node.cc b/src/cobalt/render_tree/rect_node.cc
index 7b6f656..33a7ad3 100644
--- a/src/cobalt/render_tree/rect_node.cc
+++ b/src/cobalt/render_tree/rect_node.cc
@@ -84,6 +84,23 @@
border(moved->border.Pass()),
rounded_corners(moved->rounded_corners.Pass()) {}
+bool RectNode::Builder::operator==(const Builder& other) const {
+ bool brush_equals = !background_brush && !other.background_brush;
+ if (background_brush && other.background_brush) {
+ EqualsBrushVisitor brush_equals_visitor(background_brush.get());
+ other.background_brush->Accept(&brush_equals_visitor);
+ brush_equals = brush_equals_visitor.result();
+ }
+ bool border_equals = (!border && !other.border) ||
+ (border && other.border && *border == *other.border);
+ bool rounded_corners_equals = (!rounded_corners && !other.rounded_corners) ||
+ (rounded_corners && other.rounded_corners &&
+ *rounded_corners == *other.rounded_corners);
+
+ return rect == other.rect && brush_equals && border_equals &&
+ rounded_corners_equals;
+}
+
void RectNode::Accept(NodeVisitor* visitor) { visitor->Visit(this); }
math::RectF RectNode::GetBounds() const { return data_.rect; }
diff --git a/src/cobalt/render_tree/rect_node.h b/src/cobalt/render_tree/rect_node.h
index d896aef..13ab23a 100644
--- a/src/cobalt/render_tree/rect_node.h
+++ b/src/cobalt/render_tree/rect_node.h
@@ -52,6 +52,8 @@
explicit Builder(const Builder& other);
explicit Builder(Moved moved);
+ bool operator==(const Builder& other) const;
+
// The destination rectangle (size includes border).
math::RectF rect;
diff --git a/src/cobalt/render_tree/rect_shadow_node.h b/src/cobalt/render_tree/rect_shadow_node.h
index 22344ad..de752f5 100644
--- a/src/cobalt/render_tree/rect_shadow_node.h
+++ b/src/cobalt/render_tree/rect_shadow_node.h
@@ -38,6 +38,11 @@
float spread)
: rect(rect), shadow(shadow), inset(inset), spread(spread) {}
+ bool operator==(const Builder& other) const {
+ return rect == other.rect && rounded_corners == other.rounded_corners &&
+ shadow == other.shadow && inset == other.inset &&
+ spread == other.spread;
+ }
// The destination rectangle.
math::RectF rect;
diff --git a/src/cobalt/render_tree/rounded_corners.h b/src/cobalt/render_tree/rounded_corners.h
index cb630e8..19d1f4c 100644
--- a/src/cobalt/render_tree/rounded_corners.h
+++ b/src/cobalt/render_tree/rounded_corners.h
@@ -80,6 +80,12 @@
bottom_right(bottom_right),
bottom_left(bottom_left) {}
+ bool operator==(const RoundedCorners& other) const {
+ return top_left == other.top_left && top_right == other.top_right &&
+ bottom_right == other.bottom_right &&
+ bottom_left == other.bottom_left;
+ }
+
RoundedCorners Inset(float left, float top, float right, float bottom) const {
return RoundedCorners(
top_left.Inset(left, top), top_right.Inset(right, top),
diff --git a/src/cobalt/render_tree/shadow.h b/src/cobalt/render_tree/shadow.h
index caefb8a..057c30a 100644
--- a/src/cobalt/render_tree/shadow.h
+++ b/src/cobalt/render_tree/shadow.h
@@ -32,6 +32,11 @@
const ColorRGBA& color)
: offset(offset), blur_sigma(blur_sigma), color(color) {}
+ bool operator==(const Shadow& other) const {
+ return offset == other.offset && blur_sigma == other.blur_sigma &&
+ color == other.color;
+ }
+
// Since the blur parameters represent standard deviations, most of the
// blur is contained within 3 of them, so report that as the extent of the
// blur.
diff --git a/src/cobalt/render_tree/text_node.h b/src/cobalt/render_tree/text_node.h
index 2dde330..3d35b43 100644
--- a/src/cobalt/render_tree/text_node.h
+++ b/src/cobalt/render_tree/text_node.h
@@ -46,6 +46,11 @@
const scoped_refptr<GlyphBuffer>& glyph_buffer,
const ColorRGBA& color);
+ bool operator==(const Builder& other) const {
+ return offset == other.offset && glyph_buffer == other.glyph_buffer &&
+ color == other.color && shadows == other.shadows;
+ }
+
math::Vector2dF offset;
// All of the glyph data needed to render the text.
diff --git a/src/cobalt/render_tree/viewport_filter.h b/src/cobalt/render_tree/viewport_filter.h
index 92f3d58..e4a0afa 100644
--- a/src/cobalt/render_tree/viewport_filter.h
+++ b/src/cobalt/render_tree/viewport_filter.h
@@ -33,6 +33,11 @@
const RoundedCorners& rounded_corners)
: viewport_(viewport), rounded_corners_(rounded_corners) {}
+ bool operator==(const ViewportFilter& other) const {
+ return viewport_ == other.viewport_ &&
+ rounded_corners_ == other.rounded_corners_;
+ }
+
const math::RectF& viewport() const { return viewport_; }
bool has_rounded_corners() const { return !!rounded_corners_; }
diff --git a/src/cobalt/renderer/copy_font_data.gypi b/src/cobalt/renderer/copy_font_data.gypi
index b62574e..0102e42 100644
--- a/src/cobalt/renderer/copy_font_data.gypi
+++ b/src/cobalt/renderer/copy_font_data.gypi
@@ -21,22 +21,9 @@
'source_font_files_dir': '<(static_contents_source_dir)/fonts/font_files',
'conditions': [
- [ 'cobalt_font_package == "expanded"', {
- 'source_font_config_dir': '<(static_contents_source_dir)/fonts/config/common',
-
- 'package_named_sans_serif': 4,
- 'package_named_serif': 3,
- 'package_named_fcc_fonts': 2,
- 'package_fallback_lang_non_cjk': 2,
- 'package_fallback_lang_cjk': 2,
- 'package_fallback_lang_cjk_low_quality': 0,
- 'package_fallback_lang_jp': 0,
- 'package_fallback_emoji': 1,
- 'package_fallback_symbols': 1,
- }],
-
# 'unlimited' is deprecated but is mapped to 'standard'
- [ 'cobalt_font_package == "standard" or cobalt_font_package == "unlimited"', {
+ # 'expanded' also currently maps to 'standard' but this may change in the future
+ [ 'cobalt_font_package == "standard" or cobalt_font_package == "unlimited" or cobalt_font_package == "expanded"', {
'source_font_config_dir': '<(static_contents_source_dir)/fonts/config/common',
'package_named_sans_serif': 4,
@@ -45,13 +32,12 @@
'package_fallback_lang_non_cjk': 2,
'package_fallback_lang_cjk': 1,
'package_fallback_lang_cjk_low_quality': 0,
- 'package_fallback_lang_jp': 0,
'package_fallback_emoji': 1,
'package_fallback_symbols': 1,
}],
- # '10megabytes' is deprecated but is mapped to 'limited_with_jp'
- [ 'cobalt_font_package == "limited_with_jp" or cobalt_font_package == "10megabytes"', {
+ # '10megabytes' and 'limited_with_jp' are deprecated but map to 'limited'
+ [ 'cobalt_font_package == "limited" or cobalt_font_package == "limited_with_jp" or cobalt_font_package == "10megabytes"', {
'source_font_config_dir': '<(static_contents_source_dir)/fonts/config/common',
'package_named_sans_serif': 2,
@@ -60,21 +46,6 @@
'package_fallback_lang_non_cjk': 1,
'package_fallback_lang_cjk': 0,
'package_fallback_lang_cjk_low_quality': 1,
- 'package_fallback_lang_jp': 1,
- 'package_fallback_emoji': 1,
- 'package_fallback_symbols': 1,
- }],
-
- [ 'cobalt_font_package == "limited"', {
- 'source_font_config_dir': '<(static_contents_source_dir)/fonts/config/common',
-
- 'package_named_sans_serif': 2,
- 'package_named_serif': 0,
- 'package_named_fcc_fonts': 0,
- 'package_fallback_lang_non_cjk': 1,
- 'package_fallback_lang_cjk': 0,
- 'package_fallback_lang_cjk_low_quality': 1,
- 'package_fallback_lang_jp': 0,
'package_fallback_emoji': 1,
'package_fallback_symbols': 1,
}],
@@ -88,7 +59,6 @@
'package_fallback_lang_non_cjk': 0,
'package_fallback_lang_cjk': 0,
'package_fallback_lang_cjk_low_quality': 0,
- 'package_fallback_lang_jp': 0,
'package_fallback_emoji': 0,
'package_fallback_symbols': 0,
}],
@@ -108,7 +78,6 @@
'package_fallback_lang_non_cjk': 0,
'package_fallback_lang_cjk': 0,
'package_fallback_lang_cjk_low_quality': 0,
- 'package_fallback_lang_jp': 0,
'package_fallback_emoji': 1,
'package_fallback_symbols': 0,
}],
@@ -131,9 +100,6 @@
[ 'cobalt_font_package_override_fallback_lang_cjk_low_quality >= 0', {
'package_fallback_lang_cjk_low_quality': '<(cobalt_font_package_override_fallback_lang_cjk_low_quality)',
}],
- [ 'cobalt_font_package_override_fallback_lang_jp >= 0', {
- 'package_fallback_lang_jp': '<(cobalt_font_package_override_fallback_lang_jp)',
- }],
[ 'cobalt_font_package_override_fallback_emoji >= 0', {
'package_fallback_emoji': '<(cobalt_font_package_override_fallback_emoji)',
}],
@@ -309,21 +275,11 @@
'<(source_font_files_dir)/NotoSansCJK-Regular.ttc',
],
}],
- [ 'package_fallback_lang_cjk >= 2', {
- 'files+': [
- '<(source_font_files_dir)/NotoSansCJK-Bold.ttc',
- ],
- }],
[ 'package_fallback_lang_cjk_low_quality >= 1', {
'files+': [
'<(source_font_files_dir)/DroidSansFallback.ttf',
],
}],
- [ 'package_fallback_lang_jp >= 1', {
- 'files+': [
- '<(source_font_files_dir)/NotoSansJP-Regular.otf',
- ],
- }],
[ 'package_fallback_emoji >= 1', {
'files+': [
'<(source_font_files_dir)/NotoEmoji-Regular.ttf',
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc
index 534f38c..187e684 100644
--- a/src/cobalt/renderer/pipeline.cc
+++ b/src/cobalt/renderer/pipeline.cc
@@ -81,13 +81,22 @@
submission_disposal_thread_("Rasterizer Submission Disposal"),
submit_even_if_render_tree_is_unchanged_(
submit_even_if_render_tree_is_unchanged),
- last_render_animations_active_(false),
+ last_did_rasterize_(false),
+ last_animations_expired_(true),
+ last_stat_tracked_animations_expired_(true),
rasterize_periodic_timer_("Renderer.Rasterize.Duration",
kRasterizePeriodicTimerEntriesPerUpdate,
false /*enable_entry_list_c_val*/),
rasterize_animations_timer_("Renderer.Rasterize.Animations",
kRasterizeAnimationsTimerMaxEntries,
true /*enable_entry_list_c_val*/),
+ rasterize_periodic_interval_timer_("Renderer.Rasterize.DurationInterval",
+ kRasterizeAnimationsTimerMaxEntries,
+ true /*enable_entry_list_c_val*/),
+ rasterize_animations_interval_timer_(
+ "Renderer.Rasterize.AnimationsInterval",
+ kRasterizeAnimationsTimerMaxEntries,
+ true /*enable_entry_list_c_val*/),
new_render_tree_rasterize_count_(
"Count.Renderer.Rasterize.NewRenderTree", 0,
"Total number of new render trees rasterized."),
@@ -152,6 +161,7 @@
// must be destroyed before we shutdown the rasterizer thread since it may
// contain references to render tree nodes and resources.
last_render_tree_ = NULL;
+ last_animated_render_tree_ = NULL;
// Submit a shutdown task to the rasterizer thread so that it can shutdown
// anything that must be shutdown from that thread.
@@ -252,12 +262,13 @@
DCHECK(rasterizer_thread_checker_.CalledOnValidThread());
TRACE_EVENT0("cobalt::renderer", "Pipeline::RasterizeCurrentTree()");
- base::TimeTicks now = base::TimeTicks::Now();
- Submission submission = submission_queue_->GetCurrentSubmission(now);
+ base::TimeTicks start_rasterize_time = base::TimeTicks::Now();
+ Submission submission =
+ submission_queue_->GetCurrentSubmission(start_rasterize_time);
bool is_new_render_tree = submission.render_tree != last_render_tree_;
bool has_render_tree_changed =
- last_render_animations_active_ || is_new_render_tree;
+ !last_animations_expired_ || is_new_render_tree;
// If our render tree hasn't changed from the one that was previously
// rendered and it's okay on this system to not flip the display buffer
@@ -271,63 +282,99 @@
render_tree::animations::AnimateNode* animate_node =
base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
submission.render_tree.get());
- bool are_animations_active = animate_node->expiry() > submission.time_offset;
- // If animations are going from being inactive to active, then set the c_val
- // prior to starting the animation so that it's in the correct state while the
- // tree is being rendered.
- if (!last_render_animations_active_ && are_animations_active) {
- has_active_animations_c_val_ = true;
- }
+ // Rasterize the last submitted render tree.
+ bool did_rasterize =
+ RasterizeSubmissionToRenderTarget(submission, render_target_);
+
+ bool animations_expired = animate_node->expiry() <= submission.time_offset;
+ bool stat_tracked_animations_expired =
+ animate_node->depends_on_time_expiry() <= submission.time_offset;
+
+ UpdateRasterizeStats(did_rasterize, stat_tracked_animations_expired,
+ is_new_render_tree, start_rasterize_time,
+ base::TimeTicks::Now());
+
+ last_did_rasterize_ = did_rasterize;
+ last_animations_expired_ = animations_expired;
+ last_stat_tracked_animations_expired_ = stat_tracked_animations_expired;
+}
+
+void Pipeline::UpdateRasterizeStats(bool did_rasterize,
+ bool are_stat_tracked_animations_expired,
+ bool is_new_render_tree,
+ base::TimeTicks start_time,
+ base::TimeTicks end_time) {
+ bool last_animations_active =
+ !last_stat_tracked_animations_expired_ && last_did_rasterize_;
+ bool animations_active =
+ !are_stat_tracked_animations_expired && did_rasterize;
// The rasterization is only timed with the periodic timer when the render
// tree has changed. This ensures that the frames being timed are consistent
// between platforms that submit unchanged trees and those that don't.
- bool should_run_periodic_timer = has_render_tree_changed;
-
- // The rasterization is only timed with the animations timer when there are
- // animations to track. This applies when animations were active during either
- // the last rasterization or the current one. The reason for including the
- // last one is that if animations have just expired, then this rasterization
- // produces the final state of the animated tree.
- bool should_run_animations_timer =
- last_render_animations_active_ || are_animations_active;
-
- if (should_run_periodic_timer) {
- rasterize_periodic_timer_.Start(now);
- }
- if (should_run_animations_timer) {
- rasterize_animations_timer_.Start(now);
+ if (did_rasterize) {
+ rasterize_periodic_timer_.Start(start_time);
+ rasterize_periodic_timer_.Stop(end_time);
}
- // Rasterize the last submitted render tree.
- RasterizeSubmissionToRenderTarget(submission, render_target_);
+ if (last_animations_active || animations_active) {
+ // The rasterization is only timed with the animations timer when there are
+ // animations to track. This applies when animations were active during
+ // either the last rasterization or the current one. The reason for
+ // including the last one is that if animations have just expired, then this
+ // rasterization produces the final state of the animated tree.
+ if (did_rasterize) {
+ rasterize_animations_timer_.Start(start_time);
+ rasterize_animations_timer_.Stop(end_time);
+ }
- if (should_run_periodic_timer) {
- rasterize_periodic_timer_.Stop();
- }
- if (should_run_animations_timer) {
- rasterize_animations_timer_.Stop();
+ // If animations are going from being inactive to active, then set the c_val
+ // prior to starting the animation so that it's in the correct state while
+ // the tree is being rendered.
+ // Also, start the interval timer now. While the first entry only captures a
+ // partial interval, it's recorded to include the duration of the first
+ // submission. All subsequent entries will record a full interval.
+ if (!last_animations_active && animations_active) {
+ has_active_animations_c_val_ = true;
+ rasterize_periodic_interval_timer_.Start(start_time);
+ rasterize_animations_interval_timer_.Start(start_time);
+ }
+
+ if (!did_rasterize) {
+ // If we didn't actually rasterize anything, don't count this sample.
+ rasterize_periodic_interval_timer_.Cancel();
+ rasterize_animations_interval_timer_.Cancel();
+ } else {
+ rasterize_periodic_interval_timer_.Stop(end_time);
+ rasterize_animations_interval_timer_.Stop(end_time);
+ }
+
+ // If animations are active, then they are guaranteed at least one more
+ // interval. Start the timer to record its duration.
+ if (animations_active) {
+ rasterize_periodic_interval_timer_.Start(end_time);
+ rasterize_animations_interval_timer_.Start(end_time);
+ }
+
+ // Check for if the animations are starting or ending.
+ if (!last_animations_active && animations_active) {
+ animations_start_time_ = end_time.ToInternalValue();
+ } else if (last_animations_active && !animations_active) {
+ animations_end_time_ = end_time.ToInternalValue();
+ has_active_animations_c_val_ = false;
+ rasterize_animations_interval_timer_.Flush();
+ rasterize_animations_timer_.Flush();
+ }
}
if (is_new_render_tree) {
++new_render_tree_rasterize_count_;
- new_render_tree_rasterize_time_ = base::TimeTicks::Now().ToInternalValue();
+ new_render_tree_rasterize_time_ = end_time.ToInternalValue();
}
-
- // Check for if the animations are starting or ending.
- if (!last_render_animations_active_ && are_animations_active) {
- animations_start_time_ = base::TimeTicks::Now().ToInternalValue();
- } else if (last_render_animations_active_ && !are_animations_active) {
- animations_end_time_ = base::TimeTicks::Now().ToInternalValue();
- has_active_animations_c_val_ = false;
- rasterize_animations_timer_.Flush();
- }
-
- last_render_animations_active_ = are_animations_active;
}
-void Pipeline::RasterizeSubmissionToRenderTarget(
+bool Pipeline::RasterizeSubmissionToRenderTarget(
const Submission& submission,
const scoped_refptr<backend::RenderTarget>& render_target) {
TRACE_EVENT0("cobalt::renderer",
@@ -338,14 +385,17 @@
// |previous_animated_area_|.
if (submission.render_tree != last_render_tree_) {
last_render_tree_ = submission.render_tree;
+ last_animated_render_tree_ = NULL;
previous_animated_area_ = base::nullopt;
last_render_time_ = base::nullopt;
}
// Animate the render tree using the submitted animations.
render_tree::animations::AnimateNode* animate_node =
- base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
- submission.render_tree.get());
+ last_animated_render_tree_
+ ? last_animated_render_tree_.get()
+ : base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
+ submission.render_tree.get());
// Some animations require a GL graphics context to be current. Specifically,
// a call to SbPlayerGetCurrentFrame() may be made to get the current video
@@ -355,6 +405,12 @@
render_tree::animations::AnimateNode::AnimateResults results =
animate_node->Apply(submission.time_offset);
+ if (results.animated == last_animated_render_tree_ &&
+ !submit_even_if_render_tree_is_unchanged_) {
+ return false;
+ }
+ last_animated_render_tree_ = results.animated;
+
// Calculate a bounding box around the active animations. Union it with the
// bounding box around active animations from the previous frame, and we get
// a scissor rectangle marking the dirty regions of the screen.
@@ -370,13 +426,16 @@
// Rasterize the animated render tree.
rasterizer::Rasterizer::Options rasterizer_options;
rasterizer_options.dirty = redraw_area;
- rasterizer_->Submit(results.animated, render_target, rasterizer_options);
+ rasterizer_->Submit(
+ results.animated->source(), render_target, rasterizer_options);
if (!submission.on_rasterized_callback.is_null()) {
submission.on_rasterized_callback.Run();
}
last_render_time_ = submission.time_offset;
+
+ return true;
}
void Pipeline::InitializeRasterizerThread(
@@ -468,7 +527,8 @@
render_tree::animations::AnimateNode::AnimateResults results =
animate_node->Apply(submission.time_offset);
- std::string tree_dump = render_tree::DumpRenderTreeToString(results.animated);
+ std::string tree_dump =
+ render_tree::DumpRenderTreeToString(results.animated->source());
if (message.empty() || message == "undefined") {
// If no filename was specified, send output to the console.
LOG(INFO) << tree_dump.c_str();
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h
index 9df0d8a..a9abff7 100644
--- a/src/cobalt/renderer/pipeline.h
+++ b/src/cobalt/renderer/pipeline.h
@@ -109,10 +109,18 @@
// Rasterize the animated |render_tree_submission| to |render_target|,
// applying the time_offset in the submission to the animations.
- void RasterizeSubmissionToRenderTarget(
+ // Returns true only if a rasterization actually took place.
+ bool RasterizeSubmissionToRenderTarget(
const Submission& render_tree_submission,
const scoped_refptr<backend::RenderTarget>& render_target);
+ // Updates the rasterizer timer stats according to the |start_time| and
+ // |end_time| of the most recent rasterize call.
+ void UpdateRasterizeStats(bool did_rasterize,
+ bool are_stat_tracked_animations_expired,
+ bool is_new_render_tree, base::TimeTicks start_time,
+ base::TimeTicks end_time);
+
// This method is executed on the rasterizer thread and is responsible for
// constructing the rasterizer.
void InitializeRasterizerThread(
@@ -179,6 +187,10 @@
// Keeps track of the last rendered animated render tree.
scoped_refptr<render_tree::Node> last_render_tree_;
+
+ scoped_refptr<render_tree::animations::AnimateNode>
+ last_animated_render_tree_;
+
// Keeps track of the area of the screen that animations previously existed
// within, so that we can know which regions of the screens would be dirty
// next frame.
@@ -187,8 +199,14 @@
base::optional<base::TimeDelta> last_render_time_;
// Keep track of whether the last rendered tree had active animations. This
// allows us to skip rasterizing that render tree if we see it again and it
- // did not have active animations.
- bool last_render_animations_active_;
+ // did have expired animations.
+ bool last_animations_expired_;
+ // Keep track of whether the last rendered tree had animations that we're
+ // tracking stats on.
+ bool last_stat_tracked_animations_expired_;
+
+ // Did a rasterization take place in the last frame?
+ bool last_did_rasterize_;
// Timer tracking the amount of time spent in
// |RasterizeSubmissionToRenderTarget| when the render tree has changed.
@@ -199,6 +217,17 @@
// tracking is flushed when the animations expire.
base::CValCollectionTimerStats<base::CValDebug> rasterize_animations_timer_;
+ // Accumulates render tree rasterization interval times but does not flush
+ // them until the maximum number of samples is gathered.
+ base::CValCollectionTimerStats<base::CValPublic>
+ rasterize_periodic_interval_timer_;
+
+ // Timer tracking the amount of time spent in
+ // |RasterizeSubmissionToRenderTarget| while animations are active. The
+ // tracking is flushed when the animations expire.
+ base::CValCollectionTimerStats<base::CValPublic>
+ rasterize_animations_interval_timer_;
+
// The total number of new render trees that have been rasterized.
base::CVal<int> new_render_tree_rasterize_count_;
// The last time that a newly encountered render tree was first rasterized.
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc
index 4ad07b9..ae68696 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.cc
@@ -54,6 +54,10 @@
return stream_->duplicate();
}
+size_t SkTypeface_CobaltStream::GetStreamLength() const {
+ return stream_->getLength();
+}
+
SkTypeface_CobaltStreamProvider::SkTypeface_CobaltStreamProvider(
SkFileMemoryChunkStreamProvider* stream_provider, int face_index,
Style style, bool is_fixed_pitch, const SkString& family_name,
@@ -83,3 +87,11 @@
*face_index = face_index_;
return stream_provider_->OpenStream();
}
+
+size_t SkTypeface_CobaltStreamProvider::GetStreamLength() const {
+ DLOG(WARNING)
+ << "Requesting stream length of SkTypeface_CobaltStreamProvider. "
+ "This requires a file load and should be used sparingly.";
+ SkAutoTUnref<SkFileMemoryChunkStream> stream(stream_provider_->OpenStream());
+ return stream->getLength();
+}
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h
index 2ac66ab..5e30052 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h
+++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h
@@ -28,10 +28,12 @@
SkTypeface_Cobalt(int face_index, Style style, bool is_fixed_pitch,
const SkString& family_name);
+ virtual size_t GetStreamLength() const = 0;
+
bool synthesizes_bold() const { return synthesizes_bold_; }
protected:
- virtual void onGetFamilyName(SkString* family_name) const SK_OVERRIDE;
+ void onGetFamilyName(SkString* family_name) const SK_OVERRIDE;
int face_index_;
SkString family_name_;
@@ -46,10 +48,12 @@
SkTypeface_CobaltStream(SkStreamAsset* stream, int face_index, Style style,
bool is_fixed_pitch, const SkString& family_name);
- virtual void onGetFontDescriptor(SkFontDescriptor* descriptor,
- bool* serialize) const SK_OVERRIDE;
+ void onGetFontDescriptor(SkFontDescriptor* descriptor,
+ bool* serialize) const SK_OVERRIDE;
- virtual SkStreamAsset* onOpenStream(int* face_index) const SK_OVERRIDE;
+ SkStreamAsset* onOpenStream(int* face_index) const SK_OVERRIDE;
+
+ size_t GetStreamLength() const SK_OVERRIDE;
private:
typedef SkTypeface_Cobalt INHERITED;
@@ -64,10 +68,12 @@
Style style, bool is_fixed_pitch, const SkString& family_name,
bool disable_synthetic_bolding);
- virtual void onGetFontDescriptor(SkFontDescriptor* descriptor,
- bool* serialize) const SK_OVERRIDE;
+ void onGetFontDescriptor(SkFontDescriptor* descriptor,
+ bool* serialize) const SK_OVERRIDE;
- virtual SkStreamAsset* onOpenStream(int* face_index) const SK_OVERRIDE;
+ SkStreamAsset* onOpenStream(int* face_index) const SK_OVERRIDE;
+
+ size_t GetStreamLength() const SK_OVERRIDE;
private:
typedef SkTypeface_Cobalt INHERITED;
diff --git a/src/cobalt/renderer/rasterizer/skia/typeface.cc b/src/cobalt/renderer/rasterizer/skia/typeface.cc
index e4396fe..6e0866d 100644
--- a/src/cobalt/renderer/rasterizer/skia/typeface.cc
+++ b/src/cobalt/renderer/rasterizer/skia/typeface.cc
@@ -18,12 +18,6 @@
#include "third_party/skia/include/core/SkPaint.h"
-namespace {
-
-const uint32 kEstimatedBytesPerGlyph = 256;
-
-} // namespace
-
namespace cobalt {
namespace renderer {
namespace rasterizer {
@@ -43,7 +37,7 @@
}
uint32 SkiaTypeface::GetEstimatedSizeInBytes() const {
- return typeface_->countGlyphs() * kEstimatedBytesPerGlyph;
+ return static_cast<uint32>(typeface_->GetStreamLength());
}
scoped_refptr<render_tree::Font> SkiaTypeface::CreateFontWithSize(
diff --git a/src/cobalt/renderer/submission_queue.cc b/src/cobalt/renderer/submission_queue.cc
index 20a1e39..b7b861e 100644
--- a/src/cobalt/renderer/submission_queue.cc
+++ b/src/cobalt/renderer/submission_queue.cc
@@ -89,8 +89,11 @@
base::polymorphic_downcast<render_tree::animations::AnimateNode*>(
submission_queue_.front().render_tree.get());
- if (animate_node->expiry() <= submission_time(now) &&
- animate_node->expiry() <=
+ // Check the expiration of only animations that depend on the time
+ // parameter, since they are the only ones that will be affected by snapping
+ // time.
+ if (animate_node->depends_on_time_expiry() <= submission_time(now) &&
+ animate_node->depends_on_time_expiry() <=
latest_to_submission_time + render_time(now)) {
to_submission_time_in_ms_.SnapToTarget();
}
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
index 8951eaf..0f32130 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
@@ -108,7 +108,11 @@
packet.data = const_cast<uint8_t*>(input_buffer.data());
packet.size = input_buffer.size();
+#if LIBAVUTIL_VERSION_MAJOR > 52
+ av_frame_unref(av_frame_);
+#else // LIBAVUTIL_VERSION_MAJOR > 52
avcodec_get_frame_defaults(av_frame_);
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
int frame_decoded = 0;
int result =
avcodec_decode_audio4(codec_context_, av_frame_, &frame_decoded, &packet);
@@ -219,7 +223,11 @@
return;
}
+#if LIBAVUTIL_VERSION_MAJOR > 52
+ av_frame_ = av_frame_alloc();
+#else // LIBAVUTIL_VERSION_MAJOR > 52
av_frame_ = avcodec_alloc_frame();
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
if (av_frame_ == NULL) {
SB_LOG(ERROR) << "Unable to allocate audio frame";
TeardownCodec();
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_common.h b/src/starboard/shared/ffmpeg/ffmpeg_common.h
index 4203e91..9c63c1d 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_common.h
+++ b/src/starboard/shared/ffmpeg/ffmpeg_common.h
@@ -27,6 +27,16 @@
#include "starboard/shared/internal_only.h"
+#if !defined(LIBAVUTIL_VERSION_MAJOR)
+#error "LIBAVUTIL_VERSION_MAJOR not defined"
+#endif // !defined(LIBAVUTIL_VERSION_MAJOR)
+
+#if LIBAVUTIL_VERSION_MAJOR > 52
+#define PIX_FMT_NONE AV_PIX_FMT_NONE
+#define PIX_FMT_YUV420P AV_PIX_FMT_YUV420P
+#define PIX_FMT_YUVJ420P AV_PIX_FMT_YUVJ420P
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
+
namespace starboard {
namespace shared {
namespace ffmpeg {
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
index 2d4e659..48b5774 100644
--- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
+++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
@@ -37,6 +37,58 @@
return width * height * 3 / 2;
}
+#if LIBAVUTIL_VERSION_MAJOR > 52
+
+void ReleaseBuffer(void* opaque, uint8_t* data) {
+ SbMemorySet(data, 0, sizeof(data));
+ SbMemoryDeallocate(data);
+}
+
+int AllocateBuffer(AVCodecContext* codec_context, AVFrame* frame, int flags) {
+ if (codec_context->pix_fmt != PIX_FMT_YUV420P &&
+ codec_context->pix_fmt != PIX_FMT_YUVJ420P) {
+ SB_DLOG(WARNING) << "Unsupported pix_fmt " << codec_context->pix_fmt;
+ return AVERROR(EINVAL);
+ }
+
+ int ret =
+ av_image_check_size(codec_context->width, codec_context->height, 0, NULL);
+ if (ret < 0) {
+ return ret;
+ }
+
+ // Align to kAlignment * 2 as we will divide y_stride by 2 for u and v planes
+ size_t y_stride = AlignUp(codec_context->width, kAlignment * 2);
+ size_t uv_stride = y_stride / 2;
+ size_t aligned_height = AlignUp(codec_context->height, kAlignment * 2);
+
+ uint8_t* frame_buffer = reinterpret_cast<uint8_t*>(SbMemoryAllocateAligned(
+ kAlignment, GetYV12SizeInBytes(y_stride, aligned_height)));
+
+ frame->data[0] = frame_buffer;
+ frame->linesize[0] = y_stride;
+
+ frame->data[1] = frame_buffer + y_stride * aligned_height;
+ frame->linesize[1] = uv_stride;
+
+ frame->data[2] = frame->data[1] + uv_stride * aligned_height / 2;
+ frame->linesize[2] = uv_stride;
+
+ frame->opaque = frame;
+ frame->width = codec_context->width;
+ frame->height = codec_context->height;
+ frame->format = codec_context->pix_fmt;
+
+ frame->reordered_opaque = codec_context->reordered_opaque;
+
+ frame->buf[0] = av_buffer_create(frame_buffer,
+ GetYV12SizeInBytes(y_stride, aligned_height),
+ &ReleaseBuffer, frame->opaque, 0);
+ return 0;
+}
+
+#else // LIBAVUTIL_VERSION_MAJOR > 52
+
int AllocateBuffer(AVCodecContext* codec_context, AVFrame* frame) {
if (codec_context->pix_fmt != PIX_FMT_YUV420P &&
codec_context->pix_fmt != PIX_FMT_YUVJ420P) {
@@ -91,6 +143,7 @@
SbMemorySet(frame->data, 0, sizeof(frame->data));
}
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
} // namespace
VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec)
@@ -205,7 +258,11 @@
bool VideoDecoder::DecodePacket(AVPacket* packet) {
SB_DCHECK(packet != NULL);
+#if LIBAVUTIL_VERSION_MAJOR > 52
+ av_frame_unref(av_frame_);
+#else // LIBAVUTIL_VERSION_MAJOR > 52
avcodec_get_frame_defaults(av_frame_);
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
int frame_decoded = 0;
int result =
avcodec_decode_video2(codec_context_, av_frame_, &frame_decoded, packet);
@@ -251,8 +308,12 @@
codec_context_->thread_count = 2;
codec_context_->opaque = this;
codec_context_->flags |= CODEC_FLAG_EMU_EDGE;
+#if LIBAVUTIL_VERSION_MAJOR > 52
+ codec_context_->get_buffer2 = AllocateBuffer;
+#else // LIBAVUTIL_VERSION_MAJOR > 52
codec_context_->get_buffer = AllocateBuffer;
codec_context_->release_buffer = ReleaseBuffer;
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
codec_context_->extradata = NULL;
codec_context_->extradata_size = 0;
@@ -272,7 +333,11 @@
return;
}
+#if LIBAVUTIL_VERSION_MAJOR > 52
+ av_frame_ = av_frame_alloc();
+#else // LIBAVUTIL_VERSION_MAJOR > 52
av_frame_ = avcodec_alloc_frame();
+#endif // LIBAVUTIL_VERSION_MAJOR > 52
if (av_frame_ == NULL) {
SB_LOG(ERROR) << "Unable to allocate audio frame";
TeardownCodec();
diff --git a/src/starboard/tools/raspi/run_test.py b/src/starboard/tools/raspi/run_test.py
index 5119c3a..326b654 100755
--- a/src/starboard/tools/raspi/run_test.py
+++ b/src/starboard/tools/raspi/run_test.py
@@ -21,9 +21,11 @@
import functools
import logging
import os
+import re
import signal
import sys
import textwrap
+import time
import pexpect
@@ -32,6 +34,22 @@
_RASPI_USERNAME = 'pi'
_RASPI_PASSWORD = 'raspberry'
+# Timeouts are in seconds
+_PEXPECT_DEFAULT_TIMEOUT = 600
+_PEXPECT_EXPECT_TIMEOUT = 60
+
+
+def _CleanupProcess(process):
+ """Closes current pexpect process.
+
+ Args:
+ process: Current pexpect process.
+ """
+ if process is not None and process.isalive():
+ # Send ctrl-c to the raspi.
+ process.sendline(chr(3))
+ process.close()
+
# pylint: disable=unused-argument
def _SigIntOrSigTermHandler(process, signum, frame):
@@ -44,14 +62,11 @@
frame: Current stack frame. Passed in when the signal handler is called by
python runtime.
"""
- if process.isalive():
- # Send ctrl-c to the raspi.
- process.sendline(chr(3))
- process.close()
+ _CleanupProcess(process)
sys.exit(signum)
-def RunTest(test_path, raspi_ip, flags):
+def _RunTest(test_path, raspi_ip, flags):
"""Run a test on a Raspi machine and print relevant stdout.
Args:
@@ -61,70 +76,103 @@
Returns:
Exit status of running the test.
-
- Raises:
- ValueError: Raised if test_path is invalid.
"""
- if not os.path.isfile(test_path):
- raise ValueError('test_path ({}) must be a file.'.format(test_path))
+ return_value = 1
- sys.stdout.write('Process launched, ID={}\n'.format(os.getpid()))
- sys.stdout.flush()
+ try:
+ process = None
- test_dir_path, test_file = os.path.split(test_path)
- test_base_dir = os.path.basename(os.path.normpath(test_dir_path))
+ if not os.path.isfile(test_path):
+ raise ValueError('test_path ({}) must be a file.'.format(test_path))
- raspi_user_hostname = _RASPI_USERNAME + '@' + raspi_ip
- raspi_test_path = os.path.join(test_base_dir, test_file)
+ # This is used to strip ansi color codes from output.
+ sanitize_line_re = re.compile(r'\x1b[^m]*m')
- # rsync the test files to the raspi
- options = '-avzh --exclude obj*'
- source = test_dir_path
- destination = raspi_user_hostname + ':~/'
- command = 'rsync ' + options + ' ' + source + ' ' + destination
- process = pexpect.spawn(command, timeout=120)
+ sys.stdout.write('Process launched, ID={}\n'.format(os.getpid()))
+ sys.stdout.flush()
- signal.signal(signal.SIGINT,
- functools.partial(_SigIntOrSigTermHandler, process))
- signal.signal(signal.SIGTERM,
- functools.partial(_SigIntOrSigTermHandler, process))
+ test_dir_path, test_file = os.path.split(test_path)
+ test_base_dir = os.path.basename(os.path.normpath(test_dir_path))
- process.expect(r'\S+ password:')
- process.sendline(_RASPI_PASSWORD)
+ raspi_user_hostname = _RASPI_USERNAME + '@' + raspi_ip
+ raspi_test_path = os.path.join(test_base_dir, test_file)
- while True:
- line = process.readline()
- if line:
+ # rsync the test files to the raspi
+ options = '-avzh --exclude obj*'
+ source = test_dir_path
+ destination = raspi_user_hostname + ':~/'
+ rsync_command = 'rsync ' + options + ' ' + source + ' ' + destination
+ process = pexpect.spawn(rsync_command, timeout=_PEXPECT_DEFAULT_TIMEOUT)
+
+ signal.signal(signal.SIGINT,
+ functools.partial(_SigIntOrSigTermHandler, process))
+ signal.signal(signal.SIGTERM,
+ functools.partial(_SigIntOrSigTermHandler, process))
+
+ process.expect(r'\S+ password:', timeout=_PEXPECT_EXPECT_TIMEOUT)
+ process.sendline(_RASPI_PASSWORD)
+
+ while True:
+ line = sanitize_line_re.sub('', process.readline())
+ if line:
+ sys.stdout.write(line)
+ sys.stdout.flush()
+ else:
+ break
+
+ # ssh into the raspi and run the test
+ ssh_command = 'ssh ' + raspi_user_hostname
+ process = pexpect.spawn(ssh_command, timeout=_PEXPECT_DEFAULT_TIMEOUT)
+
+ signal.signal(signal.SIGINT,
+ functools.partial(_SigIntOrSigTermHandler, process))
+ signal.signal(signal.SIGTERM,
+ functools.partial(_SigIntOrSigTermHandler, process))
+
+ process.expect(r'\S+ password:', timeout=_PEXPECT_EXPECT_TIMEOUT)
+ process.sendline(_RASPI_PASSWORD)
+
+ # Escape command line metacharacters in the flags
+ meta_chars = '()[]{}%!^"<>&|'
+ meta_re = re.compile('(' + '|'.join(
+ re.escape(char) for char in list(meta_chars)) + ')')
+ escaped_flags = re.subn(meta_re, r'\\\1', flags)[0]
+
+ test_command = raspi_test_path + ' ' + escaped_flags
+ test_time_tag = 'TEST-{time}'.format(time=time.time())
+ test_success_tag = 'succeeded'
+ test_failure_tag = 'failed'
+ test_success_output = ' && echo ' + test_time_tag + ' ' + test_success_tag
+ test_failure_output = ' || echo ' + test_time_tag + ' ' + test_failure_tag
+ process.sendline(test_command + test_success_output + test_failure_output)
+
+ while True:
+ line = sanitize_line_re.sub('', process.readline())
+ if not line:
+ break
sys.stdout.write(line)
sys.stdout.flush()
- else:
- break
+ if line.startswith(test_time_tag):
+ if line.find(test_success_tag) != -1:
+ return_value = 0
+ break
- # ssh into the raspi and run the test
- command = 'ssh ' + raspi_user_hostname
- process = pexpect.spawn(command, timeout=120)
+ except ValueError:
+ logging.exception('Test path invalid.')
+ except pexpect.EOF:
+ logging.exception('pexpect encountered EOF while reading line.')
+ except pexpect.TIMEOUT:
+ logging.exception('pexpect timed out while reading line.')
+ # pylint: disable=W0703
+ except Exception:
+ logging.exception('Error occured while running test.')
+ finally:
+ _CleanupProcess(process)
- signal.signal(signal.SIGINT,
- functools.partial(_SigIntOrSigTermHandler, process))
- signal.signal(signal.SIGTERM,
- functools.partial(_SigIntOrSigTermHandler, process))
-
- process.expect(r'\S+ password:')
- process.sendline(_RASPI_PASSWORD)
- process.sendline(raspi_test_path + ' ' + flags)
-
- while True:
- line = process.readline()
- if line:
- sys.stdout.write(line)
- sys.stdout.flush()
- else:
- break
-
- return 0
+ return return_value
-def AddTargetFlag(parser):
+def _AddTargetFlag(parser):
"""Add target to argument parser."""
parser.add_argument(
'-t',
@@ -138,7 +186,7 @@
nargs='?')
-def AddFlagsFlag(parser, default=None):
+def _AddFlagsFlag(parser, default=None):
"""Add flags to argument parser.
Args:
@@ -173,21 +221,13 @@
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=textwrap.dedent(__doc__))
- AddFlagsFlag(parser)
- AddTargetFlag(parser)
+ _AddFlagsFlag(parser)
+ _AddTargetFlag(parser)
parser.add_argument('test_path', help='Path of test to be run.', type=str)
args = parser.parse_args()
- try:
- return_value = RunTest(
- args.test_path, raspi_ip=args.target, flags=args.flags)
- # pylint: disable=W0703
- except Exception:
- logging.exception('Error occured while running binary.')
- return_value = 1
-
- return return_value
+ return _RunTest(args.test_path, raspi_ip=args.target, flags=args.flags)
if __name__ == '__main__':
diff --git a/src/third_party/libwebp/dec/idec.c b/src/third_party/libwebp/dec/idec.c
index 5bf79d3..105a3dd 100644
--- a/src/third_party/libwebp/dec/idec.c
+++ b/src/third_party/libwebp/dec/idec.c
@@ -358,30 +358,33 @@
}
// Partition #0
-static int CopyParts0Data(WebPIDecoder* const idec) {
+static VP8StatusCode CopyParts0Data(WebPIDecoder* const idec) {
VP8Decoder* const dec = (VP8Decoder*)idec->dec_;
VP8BitReader* const br = &dec->br_;
- const size_t psize = br->buf_end_ - br->buf_;
+ const size_t part_size = br->buf_end_ - br->buf_;
MemBuffer* const mem = &idec->mem_;
assert(!idec->is_lossless_);
assert(mem->part0_buf_ == NULL);
- assert(psize > 0);
- assert(psize <= mem->part0_size_); // Format limit: no need for runtime check
+ // the following is a format limitation, no need for runtime check:
+ assert(part_size <= mem->part0_size_);
+ if (part_size == 0) { // can't have zero-size partition #0
+ return VP8_STATUS_BITSTREAM_ERROR;
+ }
if (mem->mode_ == MEM_MODE_APPEND) {
// We copy and grab ownership of the partition #0 data.
- uint8_t* const part0_buf = (uint8_t*)malloc(psize);
+ uint8_t* const part0_buf = (uint8_t*)malloc(part_size);
if (part0_buf == NULL) {
- return 0;
+ return VP8_STATUS_OUT_OF_MEMORY;
}
- memcpy(part0_buf, br->buf_, psize);
+ memcpy(part0_buf, br->buf_, part_size);
mem->part0_buf_ = part0_buf;
br->buf_ = part0_buf;
- br->buf_end_ = part0_buf + psize;
+ br->buf_end_ = part0_buf + part_size;
} else {
// Else: just keep pointers to the partition #0's data in dec_->br_.
}
- mem->start_ += psize;
- return 1;
+ mem->start_ += part_size;
+ return VP8_STATUS_OK;
}
static VP8StatusCode DecodePartition0(WebPIDecoder* const idec) {
@@ -412,8 +415,9 @@
return IDecError(idec, dec->status_);
}
- if (!CopyParts0Data(idec)) {
- return IDecError(idec, VP8_STATUS_OUT_OF_MEMORY);
+ dec->status_ = CopyParts0Data(idec);
+ if (dec->status_ != VP8_STATUS_OK) {
+ return IDecError(idec, dec->status_);
}
// Finish setting up the decoding parameters. Will call io->setup().