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