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().