diff --git a/cobalt/browser/web_module.cc b/cobalt/browser/web_module.cc index 33ca0bebc8f3..7e5f31b2922c 100644 --- a/cobalt/browser/web_module.cc +++ b/cobalt/browser/web_module.cc @@ -537,6 +537,7 @@ WebModule::Impl::Impl(web::Context* web_context, const ConstructionData& data) data.options.loader_thread_priority)); animated_image_tracker_.reset(new loader::image::AnimatedImageTracker( + web_context_->name().c_str(), data.options.animated_image_decode_thread_priority)); DCHECK_LE(0, data.options.image_cache_capacity); @@ -975,6 +976,7 @@ void WebModule::Impl::ProcessOnRenderTreeRasterized( DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); web_module_stat_tracker_->OnRenderTreeRasterized(produced_time, rasterized_time); + animated_image_tracker_->OnRenderTreeRasterized(); if (produced_time >= last_render_tree_produced_time_) { is_render_tree_rasterization_pending_ = false; } diff --git a/cobalt/demos/content/animated-webp-performance-demo/10_turtle.webp b/cobalt/demos/content/animated-webp-performance-demo/10_turtle.webp new file mode 100644 index 000000000000..800df18de949 Binary files /dev/null and b/cobalt/demos/content/animated-webp-performance-demo/10_turtle.webp differ diff --git a/cobalt/demos/content/animated-webp-performance-demo/1_fan.webp b/cobalt/demos/content/animated-webp-performance-demo/1_fan.webp new file mode 100644 index 000000000000..f982fd80c04d Binary files /dev/null and b/cobalt/demos/content/animated-webp-performance-demo/1_fan.webp differ diff --git a/cobalt/demos/content/animated-webp-performance-demo/2_heart.webp b/cobalt/demos/content/animated-webp-performance-demo/2_heart.webp new file mode 100644 index 000000000000..79cdd395bddf Binary files /dev/null and b/cobalt/demos/content/animated-webp-performance-demo/2_heart.webp differ diff --git a/cobalt/demos/content/animated-webp-performance-demo/3_cry.webp b/cobalt/demos/content/animated-webp-performance-demo/3_cry.webp new file mode 100644 index 000000000000..c045e215bf08 Binary files /dev/null and b/cobalt/demos/content/animated-webp-performance-demo/3_cry.webp differ diff --git a/cobalt/demos/content/animated-webp-performance-demo/4_clap.webp b/cobalt/demos/content/animated-webp-performance-demo/4_clap.webp new file mode 100644 index 000000000000..194a34c37194 Binary files /dev/null and b/cobalt/demos/content/animated-webp-performance-demo/4_clap.webp differ diff --git a/cobalt/demos/content/animated-webp-performance-demo/5_egg.webp b/cobalt/demos/content/animated-webp-performance-demo/5_egg.webp new file mode 100644 index 000000000000..995a4cb6b1cc Binary files /dev/null and b/cobalt/demos/content/animated-webp-performance-demo/5_egg.webp differ diff --git a/cobalt/demos/content/animated-webp-performance-demo/6_party.webp b/cobalt/demos/content/animated-webp-performance-demo/6_party.webp new file mode 100644 index 000000000000..d5dfb16f8105 Binary files /dev/null and b/cobalt/demos/content/animated-webp-performance-demo/6_party.webp differ diff --git a/cobalt/demos/content/animated-webp-performance-demo/7_rofl.webp b/cobalt/demos/content/animated-webp-performance-demo/7_rofl.webp new file mode 100644 index 000000000000..4c6c375af533 Binary files /dev/null and b/cobalt/demos/content/animated-webp-performance-demo/7_rofl.webp differ diff --git a/cobalt/demos/content/animated-webp-performance-demo/8_melt.webp b/cobalt/demos/content/animated-webp-performance-demo/8_melt.webp new file mode 100644 index 000000000000..3feba616709d Binary files /dev/null and b/cobalt/demos/content/animated-webp-performance-demo/8_melt.webp differ diff --git a/cobalt/demos/content/animated-webp-performance-demo/9_explode.webp b/cobalt/demos/content/animated-webp-performance-demo/9_explode.webp new file mode 100644 index 000000000000..3c548ceffee3 Binary files /dev/null and b/cobalt/demos/content/animated-webp-performance-demo/9_explode.webp differ diff --git a/cobalt/demos/content/animated-webp-performance-demo/index.html b/cobalt/demos/content/animated-webp-performance-demo/index.html new file mode 100644 index 000000000000..5613cc835366 --- /dev/null +++ b/cobalt/demos/content/animated-webp-performance-demo/index.html @@ -0,0 +1,166 @@ + + + + + + WebP performance test + + + + + +
+ +
+
Total images playing:
+
Total frames decoded:
+
Total frames underrun:
+
Total frames overrun:
+
Frames decoded per second:
+
Underrun %: + Overrun %:
+
+ +
+ + + diff --git a/cobalt/doc/cvals.md b/cobalt/doc/cvals.md index 6accd8f3282c..68317e45d403 100644 --- a/cobalt/doc/cvals.md +++ b/cobalt/doc/cvals.md @@ -119,6 +119,18 @@ Here are examples of its usage: compatibility violations encountered. * **Count.XHR** - The total number of xhr::XMLHttpRequest in existence globally. +* **Count.MainWebModule.AnimatedImage.Active** - The total number of currently + active animated image decoders. Same image from a single URL source rendered + multiple times across the content counts as one decoder. +* **Count.MainWebModule.AnimatedImage.DecodedFrames** - Total number of decoded + frames across all active image decoders. This number resets only when + WebModule gets re-created - e.g. page reload, navigation. +* **Count.MainWebModule.AnimatedImage.DecodingUnderruns** - Total number of + frames when animated image decoding has fallen behind real-time, as defined + by image frame exposure times. This indicates a CPU bottleneck. +* **Count.MainWebModule.AnimatedImage.DecodingOverruns** - Total number of + frames when animated image decoding has been attempted too early, before + next frame exposure time. This indicates a timing issue in platform. ### Event diff --git a/cobalt/loader/image/animated_image_tracker.cc b/cobalt/loader/image/animated_image_tracker.cc index 98dadcad0dca..c7a3e73cefb5 100644 --- a/cobalt/loader/image/animated_image_tracker.cc +++ b/cobalt/loader/image/animated_image_tracker.cc @@ -15,7 +15,9 @@ #include "cobalt/loader/image/animated_image_tracker.h" #include +#include +#include "base/strings/stringprintf.h" #include "base/trace_event/trace_event.h" #include "cobalt/base/polymorphic_downcast.h" @@ -24,8 +26,22 @@ namespace loader { namespace image { AnimatedImageTracker::AnimatedImageTracker( + const char* name, base::ThreadPriority animated_image_decode_thread_priority) - : animated_image_decode_thread_("AnimatedImage") { + : animated_image_decode_thread_("AnimatedImage"), + name_(name), + count_animated_images_active( + base::StringPrintf("Count.%s.AnimatedImage.Active", name), 0, + "Total number of active animated image decoders."), + count_animated_frames_decoded( + base::StringPrintf("Count.%s.AnimatedImage.DecodedFrames", name), 0, + "Total number of decoded animated image frames."), + count_animated_frames_decoding_underrun( + base::StringPrintf("Count.%s.AnimatedImage.DecodingUnderruns", name), + 0, "Number of underruns from decoding animated images"), + count_animated_frames_decoding_overrun( + base::StringPrintf("Count.%s.AnimatedImage.DecodingOverruns", name), + 0, "Number of overruns from decoding animated images") { TRACE_EVENT0("cobalt::loader::image", "AnimatedImageTracker::RecordImage()"); base::Thread::Options options(base::MessageLoop::TYPE_DEFAULT, 0 /* default stack size */); @@ -123,6 +139,17 @@ void AnimatedImageTracker::Reset() { playing_urls_.clear(); } +void AnimatedImageTracker::OnRenderTreeRasterized() { + count_animated_images_active = playing_urls_.size(); + for (const auto& playing_url : playing_urls_) { + auto image = image_map_[playing_url.first].get(); + auto stats = image->GetFrameDeltaStats(); + count_animated_frames_decoded += stats.frames_decoded; + count_animated_frames_decoding_underrun += stats.frames_underrun; + count_animated_frames_decoding_overrun += stats.frames_overrun; + } +} + } // namespace image } // namespace loader } // namespace cobalt diff --git a/cobalt/loader/image/animated_image_tracker.h b/cobalt/loader/image/animated_image_tracker.h index f6f38e4d4092..6df920b7a331 100644 --- a/cobalt/loader/image/animated_image_tracker.h +++ b/cobalt/loader/image/animated_image_tracker.h @@ -20,6 +20,7 @@ #include "base/containers/small_map.h" #include "base/threading/thread.h" +#include "cobalt/base/c_val.h" #include "cobalt/base/unused.h" #include "cobalt/loader/image/image.h" #include "url/gurl.h" @@ -33,7 +34,8 @@ namespace image { // playing status is updated hence decoding is turned on / off for it. class AnimatedImageTracker { public: - explicit AnimatedImageTracker( + AnimatedImageTracker( + const char* name, base::ThreadPriority animated_image_decode_thread_priority); ~AnimatedImageTracker(); @@ -54,6 +56,9 @@ class AnimatedImageTracker { // animations. void Reset(); + // Called from WebModule to compute image animation related stats. + void OnRenderTreeRasterized(); + private: void OnDisplayStart(loader::image::AnimatedImage* image); void OnDisplayEnd(loader::image::AnimatedImage* image); @@ -72,6 +77,15 @@ class AnimatedImageTracker { URLToIntMap current_url_counts_; URLSet playing_urls_; + // The name of the WebModule this AnimatedImage tracker belongs to, for CVals. + const std::string name_; + + // Animated image counters + base::CVal count_animated_images_active; + base::CVal count_animated_frames_decoded; + base::CVal count_animated_frames_decoding_underrun; + base::CVal count_animated_frames_decoding_overrun; + // Used to ensure that all AnimatedImageTracker methods are called on the // same thread (*not* |animated_image_decode_thread_|), the thread that we // were constructed on. diff --git a/cobalt/loader/image/animated_webp_image.cc b/cobalt/loader/image/animated_webp_image.cc index cab2ca089b01..312098de3905 100644 --- a/cobalt/loader/image/animated_webp_image.cc +++ b/cobalt/loader/image/animated_webp_image.cc @@ -146,6 +146,8 @@ void AnimatedWebPImage::PlayInternal() { return; } is_playing_ = true; + current_stats.frames_underrun = 0; + current_stats.frames_overrun = 0; if (received_first_frame_) { StartDecoding(); @@ -169,7 +171,8 @@ void AnimatedWebPImage::StopInternal() { void AnimatedWebPImage::StartDecoding() { TRACE_EVENT0("cobalt::loader::image", "AnimatedWebPImage::StartDecoding()"); lock_.AssertAcquired(); - current_frame_time_ = base::TimeTicks::Now(); + decoding_start_time_ = current_frame_time_ = base::TimeTicks::Now(); + current_stats.frames_decoded = 0; if (task_runner_->BelongsToCurrentThread()) { DecodeFrames(); } else { @@ -265,6 +268,7 @@ bool AnimatedWebPImage::DecodeOneFrame(int frame_index) { LOG(ERROR) << "Failed to decode WebP image frame."; return false; } + current_stats.frames_decoded++; } // Alpha blend the current frame on top of the buffer. @@ -349,6 +353,7 @@ bool AnimatedWebPImage::AdvanceFrame() { // Always wait for a consumer to consume the previous frame before moving // forward with decoding the next frame. if (!frame_provider_->FrameConsumed()) { + current_stats.frames_overrun++; return false; } @@ -381,6 +386,7 @@ bool AnimatedWebPImage::AdvanceFrame() { if (next_frame_time_ < current_time) { // Don't let the animation fall back for more than a frame. next_frame_time_ = current_time; + current_stats.frames_underrun++; } return true; @@ -426,6 +432,25 @@ scoped_refptr AnimatedWebPImage::GetFrameForDebugging( return target_canvas; } +AnimatedImage::AnimatedImageDecodingStats +AnimatedWebPImage::GetFrameDeltaStats() { + AnimatedImageDecodingStats result; + if (current_stats.frames_decoded >= last_stats.frames_decoded) { + result.frames_decoded = + current_stats.frames_decoded - last_stats.frames_decoded; + result.frames_underrun = + current_stats.frames_underrun - last_stats.frames_underrun; + result.frames_overrun = + current_stats.frames_overrun - last_stats.frames_overrun; + } else { + // There was a reset somewhere + // Simply return total, this discards any overflow data we might have had. + result = current_stats; + } + last_stats = current_stats; + return result; +} + } // namespace image } // namespace loader } // namespace cobalt diff --git a/cobalt/loader/image/animated_webp_image.h b/cobalt/loader/image/animated_webp_image.h index 135bf6a600d1..4d1be0402d7d 100644 --- a/cobalt/loader/image/animated_webp_image.h +++ b/cobalt/loader/image/animated_webp_image.h @@ -66,6 +66,8 @@ class AnimatedWebPImage : public AnimatedImage { // Returns the render image of the frame for debugging scoped_refptr GetFrameForDebugging(int target_frame); + AnimatedImageDecodingStats GetFrameDeltaStats() override; + private: ~AnimatedWebPImage() override; @@ -119,12 +121,17 @@ class AnimatedWebPImage : public AnimatedImage { base::CancelableClosure decode_closure_; base::TimeTicks current_frame_time_; base::Optional next_frame_time_; + // The original encoded data. std::vector data_buffer_; scoped_refptr current_canvas_; scoped_refptr frame_provider_; base::Lock lock_; + base::TimeTicks decoding_start_time_; + AnimatedImageDecodingStats current_stats; + AnimatedImageDecodingStats last_stats; + // Makes sure that the thread that sets the task_runner is always consistent. // This is the thread sending Play()/Stop() calls, and is not necessarily // the same thread that the task_runner itself is running on. diff --git a/cobalt/loader/image/image.h b/cobalt/loader/image/image.h index af99363a3201..bac9849af3c3 100644 --- a/cobalt/loader/image/image.h +++ b/cobalt/loader/image/image.h @@ -145,6 +145,16 @@ class AnimatedImage : public Image { image_node_builder->destination_rect = destination_rect; image_node_builder->local_transform = local_transform; } + + // Frame counters for decoding. + struct AnimatedImageDecodingStats { + unsigned int frames_decoded = 0; + unsigned int frames_underrun = 0; + unsigned int frames_overrun = 0; + }; + + // Returns decoded frame stats since the last call, as a delta. + virtual AnimatedImageDecodingStats GetFrameDeltaStats() = 0; }; } // namespace image