summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkbr@chromium.org <kbr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-15 08:24:56 +0000
committerkbr@chromium.org <kbr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-15 08:24:56 +0000
commit7d08a93537fedd6c349f2194d7e2ba0d22b39e35 (patch)
treee68f1d4985f2334e54dc8dd412c0200ab2f7963a
parent0d30d93182920e565fa76eaf49053fd9ac07cc6f (diff)
downloadchromium_src-7d08a93537fedd6c349f2194d7e2ba0d22b39e35.zip
chromium_src-7d08a93537fedd6c349f2194d7e2ba0d22b39e35.tar.gz
chromium_src-7d08a93537fedd6c349f2194d7e2ba0d22b39e35.tar.bz2
Fix race conditions in window snapshot code.
Use LatencyInfo to track when a rendered frame reaches the screen. Only tested so far on Linux with single-threaded compositor. Must be tested with Telemetry's GpuTabTest.testScreenshot test on all of Windows, Mac and Linux with the compositor disabled, enabled and single-threaded, and enabled and multi-threaded. BUG=256848 Review URL: https://codereview.chromium.org/23694031 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@228652 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--cc/test/layer_tree_test.cc14
-rw-r--r--cc/test/layer_tree_test.h2
-rw-r--r--cc/trees/layer_tree_host.cc13
-rw-r--r--cc/trees/layer_tree_host.h3
-rw-r--r--cc/trees/layer_tree_host_unittest.cc93
-rw-r--r--cc/trees/layer_tree_impl.cc8
-rw-r--r--cc/trees/layer_tree_impl.h4
-rw-r--r--content/browser/aura/software_browser_compositor_output_surface.cc8
-rw-r--r--content/browser/renderer_host/render_process_host_impl.cc2
-rw-r--r--content/browser/renderer_host/render_view_host_impl.cc25
-rw-r--r--content/browser/renderer_host/render_view_host_impl.h1
-rw-r--r--content/browser/renderer_host/render_widget_host_impl.cc62
-rw-r--r--content/browser/renderer_host/render_widget_host_impl.h2
-rw-r--r--content/browser/renderer_host/render_widget_host_view_gtk.cc4
-rw-r--r--content/common/view_messages.h9
-rw-r--r--content/renderer/gpu/render_widget_compositor.cc5
-rw-r--r--content/renderer/gpu/render_widget_compositor.h3
-rw-r--r--content/renderer/render_thread_impl.cc11
-rw-r--r--content/renderer/render_thread_impl.h9
-rw-r--r--content/renderer/render_view_impl.cc35
-rw-r--r--content/renderer/render_view_impl.h3
-rw-r--r--content/renderer/render_widget.cc41
-rw-r--r--content/renderer/render_widget.h4
-rw-r--r--tools/telemetry/telemetry/core/tab_unittest.py24
-rw-r--r--tools/telemetry/unittest_data/screenshot_sync.html145
-rw-r--r--ui/events/latency_info.h3
26 files changed, 466 insertions, 67 deletions
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 87fcb54..6a7cf50 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -414,6 +414,13 @@ void LayerTreeTest::PostSetVisibleToMainThread(bool visible) {
visible));
}
+void LayerTreeTest::PostSetNextCommitForcesRedrawToMainThread() {
+ proxy()->MainThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(&LayerTreeTest::DispatchSetNextCommitForcesRedraw,
+ main_thread_weak_ptr_));
+}
+
void LayerTreeTest::DoBeginTest() {
client_ = LayerTreeHostClientForTesting::Create(this);
@@ -558,6 +565,13 @@ void LayerTreeTest::DispatchSetVisible(bool visible) {
ScheduleComposite();
}
+void LayerTreeTest::DispatchSetNextCommitForcesRedraw() {
+ DCHECK(!proxy() || proxy()->IsMainThread());
+
+ if (layer_tree_host_)
+ layer_tree_host_->SetNextCommitForcesRedraw();
+}
+
void LayerTreeTest::DispatchComposite() {
scheduled_ = false;
diff --git a/cc/test/layer_tree_test.h b/cc/test/layer_tree_test.h
index c9e14d6..d7607fb 100644
--- a/cc/test/layer_tree_test.h
+++ b/cc/test/layer_tree_test.h
@@ -117,6 +117,7 @@ class LayerTreeTest : public testing::Test, public TestHooks {
void PostSetNeedsRedrawToMainThread();
void PostSetNeedsRedrawRectToMainThread(gfx::Rect damage_rect);
void PostSetVisibleToMainThread(bool visible);
+ void PostSetNextCommitForcesRedrawToMainThread();
void DoBeginTest();
void Timeout();
@@ -138,6 +139,7 @@ class LayerTreeTest : public testing::Test, public TestHooks {
void DispatchSetNeedsRedraw();
void DispatchSetNeedsRedrawRect(gfx::Rect damage_rect);
void DispatchSetVisible(bool visible);
+ void DispatchSetNextCommitForcesRedraw();
void DispatchComposite();
void DispatchDidAddAnimation();
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 8bbf742..6e153d1 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -133,7 +133,8 @@ LayerTreeHost::LayerTreeHost(LayerTreeHostClient* client,
partial_texture_update_requests_(0),
in_paint_layer_contents_(false),
total_frames_used_for_lcd_text_metrics_(0),
- tree_id_(s_next_tree_id++) {
+ tree_id_(s_next_tree_id++),
+ next_commit_forces_redraw_(false) {
if (settings_.accelerated_animation_enabled)
animation_registrar_ = AnimationRegistrar::Create();
s_num_layer_tree_instances++;
@@ -323,11 +324,17 @@ void LayerTreeHost::FinishCommitOnImplThread(LayerTreeHostImpl* host_impl) {
DCHECK(!host_impl->pending_tree());
host_impl->CreatePendingTree();
sync_tree = host_impl->pending_tree();
+ if (next_commit_forces_redraw_)
+ sync_tree->ForceRedrawNextActivation();
} else {
+ if (next_commit_forces_redraw_)
+ host_impl->SetFullRootLayerDamage();
contents_texture_manager_->ReduceMemory(host_impl->resource_provider());
sync_tree = host_impl->active_tree();
}
+ next_commit_forces_redraw_ = false;
+
sync_tree->set_source_frame_number(source_frame_number());
if (needs_full_tree_sync_)
@@ -565,6 +572,10 @@ void LayerTreeHost::SetNextCommitWaitsForActivation() {
proxy_->SetNextCommitWaitsForActivation();
}
+void LayerTreeHost::SetNextCommitForcesRedraw() {
+ next_commit_forces_redraw_ = true;
+}
+
void LayerTreeHost::SetAnimationEvents(scoped_ptr<AnimationEventsVector> events,
base::Time wall_clock_time) {
DCHECK(proxy_->IsMainThread());
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index e84f292..20c2660 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -215,6 +215,8 @@ class CC_EXPORT LayerTreeHost : NON_EXPORTED_BASE(public RateLimiterClient) {
void SetNextCommitWaitsForActivation();
+ void SetNextCommitForcesRedraw();
+
void SetAnimationEvents(scoped_ptr<AnimationEventsVector> events,
base::Time wall_clock_time);
@@ -456,6 +458,7 @@ class CC_EXPORT LayerTreeHost : NON_EXPORTED_BASE(public RateLimiterClient) {
};
LCDTextMetrics lcd_text_metrics_;
int tree_id_;
+ bool next_commit_forces_redraw_;
scoped_refptr<Layer> page_scale_layer_;
scoped_refptr<Layer> inner_viewport_scroll_layer_;
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 535f271..8afd94b 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -637,6 +637,99 @@ class LayerTreeHostTestCompositeAndReadbackAfterForcedDraw
MULTI_THREAD_TEST_F(LayerTreeHostTestCompositeAndReadbackAfterForcedDraw);
+class LayerTreeHostTestSetNextCommitForcesRedraw : public LayerTreeHostTest {
+ public:
+ LayerTreeHostTestSetNextCommitForcesRedraw()
+ : num_draws_(0),
+ bounds_(50, 50),
+ invalid_rect_(10, 10, 20, 20),
+ root_layer_(ContentLayer::Create(&client_)) {
+ }
+
+ virtual void BeginTest() OVERRIDE {
+ root_layer_->SetIsDrawable(true);
+ root_layer_->SetBounds(bounds_);
+ layer_tree_host()->SetRootLayer(root_layer_);
+ layer_tree_host()->SetViewportSize(bounds_);
+ PostSetNeedsCommitToMainThread();
+ }
+
+ virtual void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ if (num_draws_ == 3 && host_impl->settings().impl_side_painting)
+ host_impl->SetNeedsRedrawRect(invalid_rect_);
+ }
+
+ virtual bool PrepareToDrawOnThread(LayerTreeHostImpl* host_impl,
+ LayerTreeHostImpl::FrameData* frame_data,
+ bool result) OVERRIDE {
+ EXPECT_TRUE(result);
+
+ gfx::RectF root_damage_rect;
+ if (!frame_data->render_passes.empty())
+ root_damage_rect = frame_data->render_passes.back()->damage_rect;
+
+ switch (num_draws_) {
+ case 0:
+ EXPECT_RECT_EQ(gfx::Rect(bounds_), root_damage_rect);
+ break;
+ case 1:
+ case 2:
+ EXPECT_RECT_EQ(gfx::Rect(0, 0, 0, 0), root_damage_rect);
+ break;
+ case 3:
+ EXPECT_RECT_EQ(invalid_rect_, root_damage_rect);
+ break;
+ case 4:
+ EXPECT_RECT_EQ(gfx::Rect(bounds_), root_damage_rect);
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ return result;
+ }
+
+ virtual void DrawLayersOnThread(LayerTreeHostImpl* host_impl) OVERRIDE {
+ switch (num_draws_) {
+ case 0:
+ case 1:
+ // Cycle through a couple of empty commits to ensure we're observing the
+ // right behavior
+ PostSetNeedsCommitToMainThread();
+ break;
+ case 2:
+ // Should force full frame damage on the next commit
+ PostSetNextCommitForcesRedrawToMainThread();
+ PostSetNeedsCommitToMainThread();
+ if (host_impl->settings().impl_side_painting)
+ host_impl->BlockNotifyReadyToActivateForTesting(true);
+ else
+ num_draws_++;
+ break;
+ case 3:
+ host_impl->BlockNotifyReadyToActivateForTesting(false);
+ break;
+ default:
+ EndTest();
+ break;
+ }
+ num_draws_++;
+ }
+
+ virtual void AfterTest() OVERRIDE {
+ EXPECT_EQ(5, num_draws_);
+ }
+
+ private:
+ int num_draws_;
+ const gfx::Size bounds_;
+ const gfx::Rect invalid_rect_;
+ FakeContentLayerClient client_;
+ scoped_refptr<ContentLayer> root_layer_;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSetNextCommitForcesRedraw);
+
// If the layerTreeHost says it can't draw, Then we should not try to draw.
class LayerTreeHostTestCanDrawBlocksDrawing : public LayerTreeHostTest {
public:
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 8c87b75..471a133 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -40,7 +40,8 @@ LayerTreeImpl::LayerTreeImpl(LayerTreeHostImpl* layer_tree_host_impl)
contents_textures_purged_(false),
viewport_size_invalid_(false),
needs_update_draw_properties_(true),
- needs_full_tree_sync_(true) {
+ needs_full_tree_sync_(true),
+ next_activation_forces_redraw_(false) {
}
LayerTreeImpl::~LayerTreeImpl() {
@@ -111,6 +112,11 @@ void LayerTreeImpl::PushPropertiesTo(LayerTreeImpl* target_tree) {
// The request queue should have been processed and does not require a push.
DCHECK_EQ(ui_resource_request_queue_.size(), 0u);
+ if (next_activation_forces_redraw_) {
+ layer_tree_host_impl_->SetFullRootLayerDamage();
+ next_activation_forces_redraw_ = false;
+ }
+
target_tree->SetLatencyInfo(latency_info_);
latency_info_.Clear();
target_tree->SetPageScaleFactorAndLimits(
diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h
index 6714503..faf360f 100644
--- a/cc/trees/layer_tree_impl.h
+++ b/cc/trees/layer_tree_impl.h
@@ -164,6 +164,8 @@ class CC_EXPORT LayerTreeImpl {
void set_needs_full_tree_sync(bool needs) { needs_full_tree_sync_ = needs; }
bool needs_full_tree_sync() const { return needs_full_tree_sync_; }
+ void ForceRedrawNextActivation() { next_activation_forces_redraw_ = true; }
+
void set_ui_resource_request_queue(const UIResourceRequestQueue& queue);
const LayerImplList& RenderSurfaceLayerList() const;
@@ -261,6 +263,8 @@ class CC_EXPORT LayerTreeImpl {
// structural differences relative to the active tree.
bool needs_full_tree_sync_;
+ bool next_activation_forces_redraw_;
+
ui::LatencyInfo latency_info_;
UIResourceRequestQueue ui_resource_request_queue_;
diff --git a/content/browser/aura/software_browser_compositor_output_surface.cc b/content/browser/aura/software_browser_compositor_output_surface.cc
index f196c63..773e7df 100644
--- a/content/browser/aura/software_browser_compositor_output_surface.cc
+++ b/content/browser/aura/software_browser_compositor_output_surface.cc
@@ -4,6 +4,7 @@
#include "content/browser/aura/software_browser_compositor_output_surface.h"
+#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "cc/output/compositor_frame.h"
#include "cc/output/software_output_device.h"
@@ -21,7 +22,12 @@ void SoftwareBrowserCompositorOutputSurface::SwapBuffers(
ui::LatencyInfo latency_info = frame->metadata.latency_info;
latency_info.AddLatencyNumber(
ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, 0);
- RenderWidgetHostImpl::CompositorFrameDrawn(latency_info);
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &RenderWidgetHostImpl::CompositorFrameDrawn,
+ latency_info));
}
} // namespace content
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 442f126..26fd958 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -1289,6 +1289,8 @@ void RenderProcessHostImpl::OnChannelConnected(int32 peer_pid) {
tracked_objects::ThreadData::Status status =
tracked_objects::ThreadData::status();
Send(new ChildProcessMsg_SetProfilerStatus(status));
+
+ Send(new ViewMsg_SetRendererProcessID(GetID()));
}
void RenderProcessHostImpl::OnChannelError() {
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index ed07949..26daa67 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -68,7 +68,6 @@
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/shell_dialogs/selected_file_info.h"
-#include "ui/snapshot/snapshot.h"
#include "webkit/browser/fileapi/isolated_context.h"
#if defined(OS_MACOSX)
@@ -993,7 +992,6 @@ bool RenderViewHostImpl::OnMessageReceived(const IPC::Message& msg) {
OnSelectionBoundsChanged)
IPC_MESSAGE_HANDLER(ViewHostMsg_ScriptEvalResponse, OnScriptEvalResponse)
IPC_MESSAGE_HANDLER(ViewHostMsg_DidZoomURL, OnDidZoomURL)
- IPC_MESSAGE_HANDLER(ViewHostMsg_GetWindowSnapshot, OnGetWindowSnapshot)
IPC_MESSAGE_HANDLER(DesktopNotificationHostMsg_RequestPermission,
OnRequestDesktopNotificationPermission)
IPC_MESSAGE_HANDLER(DesktopNotificationHostMsg_Show,
@@ -1994,29 +1992,6 @@ void RenderViewHostImpl::OnDomOperationResponse(
Details<DomOperationNotificationDetails>(&details));
}
-void RenderViewHostImpl::OnGetWindowSnapshot(const int snapshot_id) {
- std::vector<unsigned char> png;
-
- // This feature is behind the kEnableGpuBenchmarking command line switch
- // because it poses security concerns and should only be used for testing.
- const CommandLine& command_line = *CommandLine::ForCurrentProcess();
- if (command_line.HasSwitch(switches::kEnableGpuBenchmarking)) {
- gfx::Rect view_bounds = GetView()->GetViewBounds();
- gfx::Rect snapshot_bounds(view_bounds.size());
- gfx::Size snapshot_size = snapshot_bounds.size();
-
- if (ui::GrabViewSnapshot(GetView()->GetNativeView(),
- &png, snapshot_bounds)) {
- Send(new ViewMsg_WindowSnapshotCompleted(
- GetRoutingID(), snapshot_id, snapshot_size, png));
- return;
- }
- }
-
- Send(new ViewMsg_WindowSnapshotCompleted(
- GetRoutingID(), snapshot_id, gfx::Size(), png));
-}
-
#if defined(OS_MACOSX) || defined(OS_ANDROID)
void RenderViewHostImpl::OnShowPopup(
const ViewHostMsg_ShowPopup_Params& params) {
diff --git a/content/browser/renderer_host/render_view_host_impl.h b/content/browser/renderer_host/render_view_host_impl.h
index 0364894..54f6f6a 100644
--- a/content/browser/renderer_host/render_view_host_impl.h
+++ b/content/browser/renderer_host/render_view_host_impl.h
@@ -583,7 +583,6 @@ class CONTENT_EXPORT RenderViewHostImpl
void OnDidAccessInitialDocument();
void OnDomOperationResponse(const std::string& json_string,
int automation_id);
- void OnGetWindowSnapshot(const int snapshot_id);
#if defined(OS_MACOSX) || defined(OS_ANDROID)
void OnShowPopup(const ViewHostMsg_ShowPopup_Params& params);
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 74fc640..5de04bb 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -5,6 +5,7 @@
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include <math.h>
+#include <set>
#include <utility>
#include "base/auto_reset.h"
@@ -57,6 +58,7 @@
#include "ui/gfx/size_conversions.h"
#include "ui/gfx/skbitmap_operations.h"
#include "ui/gfx/vector2d_conversions.h"
+#include "ui/snapshot/snapshot.h"
#include "webkit/common/cursors/webcursor.h"
#include "webkit/common/webpreferences.h"
@@ -210,6 +212,7 @@ RenderWidgetHostImpl::RenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
is_threaded_compositing_enabled_ = IsThreadedCompositingEnabled();
+
g_routing_id_widget_map.Get().insert(std::make_pair(
RenderWidgetHostID(process->GetID(), routing_id_), this));
process_->AddRoute(routing_id_, this);
@@ -2381,6 +2384,14 @@ void RenderWidgetHostImpl::ComputeTouchLatency(
}
void RenderWidgetHostImpl::FrameSwapped(const ui::LatencyInfo& latency_info) {
+ ui::LatencyInfo::LatencyComponent window_snapshot_component;
+ if (latency_info.FindLatency(ui::WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT,
+ GetLatencyComponentId(),
+ &window_snapshot_component)) {
+ WindowSnapshotReachedScreen(
+ static_cast<int>(window_snapshot_component.sequence_number));
+ }
+
ui::LatencyInfo::LatencyComponent rwh_component;
ui::LatencyInfo::LatencyComponent swap_component;
if (!latency_info.FindLatency(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
@@ -2430,23 +2441,54 @@ void RenderWidgetHostImpl::DidReceiveRendererFrame() {
view_->DidReceiveRendererFrame();
}
+void RenderWidgetHostImpl::WindowSnapshotReachedScreen(int snapshot_id) {
+ DCHECK(base::MessageLoop::current()->IsType(base::MessageLoop::TYPE_UI));
+
+ std::vector<unsigned char> png;
+
+ // This feature is behind the kEnableGpuBenchmarking command line switch
+ // because it poses security concerns and should only be used for testing.
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ if (command_line.HasSwitch(switches::kEnableGpuBenchmarking)) {
+ gfx::Rect view_bounds = GetView()->GetViewBounds();
+ gfx::Rect snapshot_bounds(view_bounds.size());
+ gfx::Size snapshot_size = snapshot_bounds.size();
+
+ if (ui::GrabViewSnapshot(GetView()->GetNativeView(),
+ &png, snapshot_bounds)) {
+ Send(new ViewMsg_WindowSnapshotCompleted(
+ GetRoutingID(), snapshot_id, snapshot_size, png));
+ return;
+ }
+ }
+
+ Send(new ViewMsg_WindowSnapshotCompleted(
+ GetRoutingID(), snapshot_id, gfx::Size(), png));
+}
+
// static
void RenderWidgetHostImpl::CompositorFrameDrawn(
const ui::LatencyInfo& latency_info) {
+ std::set<RenderWidgetHostImpl*> rwhi_set;
+
for (ui::LatencyInfo::LatencyMap::const_iterator b =
latency_info.latency_components.begin();
b != latency_info.latency_components.end();
++b) {
- if (b->first.first != ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT)
- continue;
- // Matches with GetLatencyComponentId
- int routing_id = b->first.second & 0xffffffff;
- int process_id = (b->first.second >> 32) & 0xffffffff;
- RenderWidgetHost* rwh =
- RenderWidgetHost::FromID(process_id, routing_id);
- if (!rwh)
- continue;
- RenderWidgetHostImpl::From(rwh)->FrameSwapped(latency_info);
+ if (b->first.first == ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT ||
+ b->first.first == ui::WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT) {
+ // Matches with GetLatencyComponentId
+ int routing_id = b->first.second & 0xffffffff;
+ int process_id = (b->first.second >> 32) & 0xffffffff;
+ RenderWidgetHost* rwh =
+ RenderWidgetHost::FromID(process_id, routing_id);
+ if (!rwh) {
+ continue;
+ }
+ RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(rwh);
+ if (rwhi_set.insert(rwhi).second)
+ rwhi->FrameSwapped(latency_info);
+ }
}
}
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 6882c34..b4ba0d6 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -745,6 +745,8 @@ class CONTENT_EXPORT RenderWidgetHostImpl : virtual public RenderWidgetHost,
// which may get in recursive loops).
void DelayedAutoResized();
+ void WindowSnapshotReachedScreen(int snapshot_id);
+
// Our delegate, which wants to know mainly about keyboard events.
// It will remain non-NULL until DetachDelegate() is called.
RenderWidgetHostDelegate* delegate_;
diff --git a/content/browser/renderer_host/render_widget_host_view_gtk.cc b/content/browser/renderer_host/render_widget_host_view_gtk.cc
index 4c51767..bacdde3 100644
--- a/content/browser/renderer_host/render_widget_host_view_gtk.cc
+++ b/content/browser/renderer_host/render_widget_host_view_gtk.cc
@@ -1069,7 +1069,7 @@ void RenderWidgetHostViewGtk::AcceleratedSurfaceBuffersSwapped(
ack_params.sync_point = 0;
RenderWidgetHostImpl::AcknowledgeBufferPresent(
params.route_id, gpu_host_id, ack_params);
- host_->FrameSwapped(params.latency_info);
+ RenderWidgetHostImpl::CompositorFrameDrawn(params.latency_info);
}
void RenderWidgetHostViewGtk::AcceleratedSurfacePostSubBuffer(
@@ -1079,7 +1079,7 @@ void RenderWidgetHostViewGtk::AcceleratedSurfacePostSubBuffer(
ack_params.sync_point = 0;
RenderWidgetHostImpl::AcknowledgeBufferPresent(
params.route_id, gpu_host_id, ack_params);
- host_->FrameSwapped(params.latency_info);
+ RenderWidgetHostImpl::CompositorFrameDrawn(params.latency_info);
}
void RenderWidgetHostViewGtk::AcceleratedSurfaceSuspend() {
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index eea3e79..355489b 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -806,6 +806,11 @@ IPC_MESSAGE_ROUTED2(ViewMsg_SetHistoryLengthAndPrune,
int, /* merge_history_length */
int32 /* minimum_page_id */)
+// Tells the renderer the browser's notion of its process ID.
+// Some subsystems, like LatencyInfo, require this to be known to the renderer.
+IPC_MESSAGE_CONTROL1(ViewMsg_SetRendererProcessID,
+ base::ProcessId /* process_id */)
+
// Tells the renderer to create a new view.
// This message is slightly different, the view it takes (via
// ViewMsg_New_Params) is the view to create, the message itself is sent as a
@@ -1860,10 +1865,6 @@ IPC_MESSAGE_ROUTED3(ViewHostMsg_WebUISend,
std::string /* message */,
base::ListValue /* args */)
-// Requests a snapshot of the given window.
-IPC_MESSAGE_ROUTED1(ViewHostMsg_GetWindowSnapshot,
- int /* snapshot_id */)
-
// A renderer sends this to the browser process when it wants to create a ppapi
// plugin. The browser will create the plugin process if necessary, and will
// return a handle to the channel on success.
diff --git a/content/renderer/gpu/render_widget_compositor.cc b/content/renderer/gpu/render_widget_compositor.cc
index 78622ee..858f226f 100644
--- a/content/renderer/gpu/render_widget_compositor.cc
+++ b/content/renderer/gpu/render_widget_compositor.cc
@@ -382,6 +382,11 @@ void RenderWidgetCompositor::SetNeedsRedrawRect(gfx::Rect damage_rect) {
layer_tree_host_->SetNeedsRedrawRect(damage_rect);
}
+void RenderWidgetCompositor::SetNeedsForcedRedraw() {
+ layer_tree_host_->SetNextCommitForcesRedraw();
+ setNeedsRedraw();
+}
+
void RenderWidgetCompositor::SetLatencyInfo(
const ui::LatencyInfo& latency_info) {
layer_tree_host_->SetLatencyInfo(latency_info);
diff --git a/content/renderer/gpu/render_widget_compositor.h b/content/renderer/gpu/render_widget_compositor.h
index 910d99c..8de7478 100644
--- a/content/renderer/gpu/render_widget_compositor.h
+++ b/content/renderer/gpu/render_widget_compositor.h
@@ -51,6 +51,9 @@ class RenderWidgetCompositor : public WebKit::WebLayerTreeView,
bool animate);
void SetOverdrawBottomHeight(float overdraw_bottom_height);
void SetNeedsRedrawRect(gfx::Rect damage_rect);
+ // Like setNeedsRedraw but forces the frame to be drawn, without early-outs.
+ // Redraw will be forced after the next commit
+ void SetNeedsForcedRedraw();
void SetLatencyInfo(const ui::LatencyInfo& latency_info);
int GetLayerTreeId() const;
void NotifyInputThrottledUntilCommit();
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index bc5f679..1eb2c21 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -398,6 +398,8 @@ void RenderThreadImpl::Init() {
memory_pressure_listener_.reset(new base::MemoryPressureListener(
base::Bind(&RenderThreadImpl::OnMemoryPressure, base::Unretained(this))));
+ renderer_process_id_ = base::kNullProcessId;
+
TRACE_EVENT_END_ETW("RenderThreadImpl::Init", 0, "");
}
@@ -1094,6 +1096,7 @@ bool RenderThreadImpl::OnControlMessageReceived(const IPC::Message& msg) {
IPC_MESSAGE_HANDLER(ViewMsg_PurgePluginListCache, OnPurgePluginListCache)
IPC_MESSAGE_HANDLER(ViewMsg_NetworkStateChanged, OnNetworkStateChanged)
IPC_MESSAGE_HANDLER(ViewMsg_TempCrashWithData, OnTempCrashWithData)
+ IPC_MESSAGE_HANDLER(ViewMsg_SetRendererProcessID, OnSetRendererProcessID)
IPC_MESSAGE_HANDLER(ViewMsg_SetWebKitSharedTimersSuspended,
OnSetWebKitSharedTimersSuspended)
IPC_MESSAGE_UNHANDLED(handled = false)
@@ -1231,6 +1234,10 @@ void RenderThreadImpl::OnTempCrashWithData(const GURL& data) {
CHECK(false);
}
+void RenderThreadImpl::OnSetRendererProcessID(base::ProcessId process_id) {
+ renderer_process_id_ = process_id;
+}
+
void RenderThreadImpl::OnSetWebKitSharedTimersSuspended(bool suspend) {
ToggleWebKitSharedTimer(suspend);
}
@@ -1295,4 +1302,8 @@ void RenderThreadImpl::SampleGamepads(WebKit::WebGamepads* data) {
gamepad_shared_memory_reader_->SampleGamepads(*data);
}
+base::ProcessId RenderThreadImpl::renderer_process_id() const {
+ return renderer_process_id_;
+}
+
} // namespace content
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index 59b7c1d..aea1836 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -11,6 +11,7 @@
#include "base/memory/memory_pressure_listener.h"
#include "base/observer_list.h"
+#include "base/process/process_handle.h"
#include "base/strings/string16.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
@@ -343,6 +344,11 @@ class CONTENT_EXPORT RenderThreadImpl : public RenderThread,
// Retrieve current gamepad data.
void SampleGamepads(WebKit::WebGamepads* data);
+ // Get the browser process's notion of the renderer process's ID.
+ // This is the first argument to RenderWidgetHost::FromID. Ideally
+ // this would be available on all platforms via base::Process.
+ base::ProcessId renderer_process_id() const;
+
private:
// ChildThread
virtual bool OnControlMessageReceived(const IPC::Message& msg) OVERRIDE;
@@ -374,6 +380,7 @@ class CONTENT_EXPORT RenderThreadImpl : public RenderThread,
void OnNetworkStateChanged(bool online);
void OnGetAccessibilityTree();
void OnTempCrashWithData(const GURL& data);
+ void OnSetRendererProcessID(base::ProcessId process_id);
void OnSetWebKitSharedTimersSuspended(bool suspend);
void OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
@@ -486,6 +493,8 @@ class CONTENT_EXPORT RenderThreadImpl : public RenderThread,
scoped_ptr<GamepadSharedMemoryReader> gamepad_shared_memory_reader_;
+ base::ProcessId renderer_process_id_;
+
DISALLOW_COPY_AND_ASSIGN(RenderThreadImpl);
};
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index dff71f2..557090d 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -23,6 +23,7 @@
#include "base/metrics/histogram.h"
#include "base/path_service.h"
#include "base/process/kill.h"
+#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
@@ -193,6 +194,7 @@
#include "third_party/WebKit/public/web/WebWindowFeatures.h"
#include "third_party/WebKit/public/web/default/WebRenderTheme.h"
#include "ui/base/ui_base_switches_util.h"
+#include "ui/events/latency_info.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
@@ -2253,15 +2255,32 @@ bool RenderViewImpl::SendAndRunNestedMessageLoop(IPC::SyncMessage* message) {
void RenderViewImpl::GetWindowSnapshot(const WindowSnapshotCallback& callback) {
int id = next_snapshot_id_++;
pending_snapshots_.insert(std::make_pair(id, callback));
- Send(new ViewHostMsg_GetWindowSnapshot(routing_id_, id));
+ ui::LatencyInfo latency_info;
+ latency_info.AddLatencyNumber(ui::WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT,
+ GetLatencyComponentId(),
+ id);
+ if (RenderWidgetCompositor* rwc = compositor()) {
+ rwc->SetLatencyInfo(latency_info);
+ } else {
+ latency_info_.MergeWith(latency_info);
+ }
+ ScheduleCompositeWithForcedRedraw();
}
void RenderViewImpl::OnWindowSnapshotCompleted(const int snapshot_id,
const gfx::Size& size, const std::vector<unsigned char>& png) {
- PendingSnapshotMap::iterator it = pending_snapshots_.find(snapshot_id);
- DCHECK(it != pending_snapshots_.end());
- it->second.Run(size, png);
- pending_snapshots_.erase(it);
+
+ // Any pending snapshots with a lower ID than the one received are considered
+ // to be implicitly complete, and returned the same snapshot data.
+ PendingSnapshotMap::iterator it = pending_snapshots_.begin();
+ while(it != pending_snapshots_.end()) {
+ if (it->first <= snapshot_id) {
+ it->second.Run(size, png);
+ pending_snapshots_.erase(it++);
+ } else {
+ ++it;
+ }
+ }
}
// WebKit::WebViewClient ------------------------------------------------------
@@ -2746,6 +2765,12 @@ gfx::RectF RenderViewImpl::ClientRectToPhysicalWindowRect(
return window_rect;
}
+int64 RenderViewImpl::GetLatencyComponentId() {
+ // Note: this must match the logic in RenderWidgetHostImpl.
+ return GetRoutingID() | (static_cast<int64>(
+ RenderThreadImpl::current()->renderer_process_id()) << 32);
+}
+
void RenderViewImpl::StartNavStateSyncTimerIfNecessary() {
// No need to update state if no page has committed yet.
if (page_id_ == -1)
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 6b0f1c4a..13d69f7 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -1166,6 +1166,9 @@ class CONTENT_EXPORT RenderViewImpl
gfx::RectF ClientRectToPhysicalWindowRect(const gfx::RectF& rect) const;
+ // Helper for LatencyInfo construction.
+ int64 GetLatencyComponentId();
+
// ---------------------------------------------------------------------------
// ADDING NEW FUNCTIONS? Please keep private functions alphabetized and put
// it in the same order in the .cc file as it was in the header.
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index e1b7353..1cb162a 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -582,6 +582,34 @@ void RenderWidget::OnShowHostContextMenu(ContextMenuParams* params) {
screen_metrics_emulator_->OnShowContextMenu(params);
}
+void RenderWidget::ScheduleCompositeWithForcedRedraw() {
+ if (compositor_) {
+ // Regardless of whether threaded compositing is enabled, always
+ // use this mechanism to force the compositor to redraw. However,
+ // the invalidation code path below is still needed for the
+ // non-threaded case.
+ compositor_->SetNeedsForcedRedraw();
+ }
+ ScheduleCompositeImpl(true);
+}
+
+void RenderWidget::ScheduleCompositeImpl(bool force_redraw) {
+ if (RenderThreadImpl::current()->compositor_message_loop_proxy().get() &&
+ compositor_) {
+ if (!force_redraw) {
+ compositor_->setNeedsRedraw();
+ }
+ } else {
+ // TODO(nduca): replace with something a little less hacky. The reason this
+ // hack is still used is because the Invalidate-DoDeferredUpdate loop
+ // contains a lot of host-renderer synchronization logic that is still
+ // important for the accelerated compositing case. The option of simply
+ // duplicating all that code is less desirable than "faking out" the
+ // invalidation path using a magical damage rect.
+ didInvalidateRect(WebRect(0, 0, 1, 1));
+ }
+}
+
bool RenderWidget::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(RenderWidget, message)
@@ -1883,18 +1911,7 @@ void RenderWidget::didCompleteSwapBuffers() {
}
void RenderWidget::scheduleComposite() {
- if (RenderThreadImpl::current()->compositor_message_loop_proxy().get() &&
- compositor_) {
- compositor_->setNeedsRedraw();
- } else {
- // TODO(nduca): replace with something a little less hacky. The reason this
- // hack is still used is because the Invalidate-DoDeferredUpdate loop
- // contains a lot of host-renderer synchronization logic that is still
- // important for the accelerated compositing case. The option of simply
- // duplicating all that code is less desirable than "faking out" the
- // invalidation path using a magical damage rect.
- didInvalidateRect(WebRect(0, 0, 1, 1));
- }
+ ScheduleCompositeImpl(false);
}
void RenderWidget::scheduleAnimation() {
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 8341b0a..3987064 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -241,6 +241,8 @@ class CONTENT_EXPORT RenderWidget
void DisableScreenMetricsEmulation();
void SetPopupOriginAdjustmentsForEmulation(ScreenMetricsEmulator* emulator);
+ void ScheduleCompositeWithForcedRedraw();
+
protected:
// Friend RefCounted so that the dtor can be non-public. Using this class
// without ref-counting is an error.
@@ -547,6 +549,8 @@ class CONTENT_EXPORT RenderWidget
bool OnSnapshotHelper(const gfx::Rect& src_subrect, SkBitmap* bitmap);
+ void ScheduleCompositeImpl(bool force_redraw);
+
// Routing ID that allows us to communicate to the parent browser process
// RenderWidgetHost. When MSG_ROUTING_NONE, no messages may be sent.
int32 routing_id_;
diff --git a/tools/telemetry/telemetry/core/tab_unittest.py b/tools/telemetry/telemetry/core/tab_unittest.py
index 3bc3089..b47cd44 100644
--- a/tools/telemetry/telemetry/core/tab_unittest.py
+++ b/tools/telemetry/telemetry/core/tab_unittest.py
@@ -3,12 +3,10 @@
# found in the LICENSE file.
import logging
-import time
from telemetry.core import util
from telemetry.core import exceptions
from telemetry.unittest import tab_test_case
-from telemetry.unittest import DisabledTest
def _IsDocumentVisible(tab):
@@ -57,7 +55,6 @@ class GpuTabTest(tab_test_case.TabTestCase):
self._extra_browser_args = ['--enable-gpu-benchmarking']
super(GpuTabTest, self).setUp()
- @DisabledTest
def testScreenshot(self):
if not self._tab.screenshot_supported:
logging.warning('Browser does not support screenshots, skipping test.')
@@ -69,8 +66,6 @@ class GpuTabTest(tab_test_case.TabTestCase):
self._tab.WaitForDocumentReadyStateToBeComplete()
pixel_ratio = self._tab.EvaluateJavaScript('window.devicePixelRatio || 1')
- # TODO(bajones): Sleep for a bit to counter BUG 260878.
- time.sleep(0.5)
screenshot = self._tab.Screenshot(5)
assert screenshot
screenshot.GetPixelColor(0 * pixel_ratio, 0 * pixel_ratio).AssertIsRGB(
@@ -79,3 +74,22 @@ class GpuTabTest(tab_test_case.TabTestCase):
0, 255, 0, tolerance=2)
screenshot.GetPixelColor(32 * pixel_ratio, 32 * pixel_ratio).AssertIsRGB(
255, 255, 255, tolerance=2)
+
+ def testScreenshotSync(self):
+ if not self._tab.screenshot_supported:
+ logging.warning('Browser does not support screenshots, skipping test.')
+ return
+
+ self._browser.SetHTTPServerDirectories(util.GetUnittestDataDir())
+ self._tab.Navigate(
+ self._browser.http_server.UrlOf('screenshot_sync.html'))
+ self._tab.WaitForDocumentReadyStateToBeComplete()
+
+ def IsTestComplete():
+ return self._tab.EvaluateJavaScript('window.__testComplete')
+ util.WaitFor(IsTestComplete, 120)
+
+ message = self._tab.EvaluateJavaScript('window.__testMessage')
+ if message:
+ logging.error(message)
+ assert self._tab.EvaluateJavaScript('window.__testSuccess')
diff --git a/tools/telemetry/unittest_data/screenshot_sync.html b/tools/telemetry/unittest_data/screenshot_sync.html
new file mode 100644
index 0000000..2c4bd10
--- /dev/null
+++ b/tools/telemetry/unittest_data/screenshot_sync.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Synchronized screenshot test</title>
+<style>
+ html, body { margin: 0; }
+ #log { height: 150px; overflow: auto; width: 512px; }
+ #log.failed { background-color: #FFAAAA; }
+</style>
+</head>
+<body>
+ <canvas id="src-canvas" width="256" height="256"></canvas>
+ <canvas id="dest-canvas" width="256" height="256"></canvas><br/>
+ <div id="log"></div>
+
+ <script>
+ "use strict";
+
+ (function () {
+ window.__testComplete = false;
+ window.__testMessage = '';
+ window.__testSuccess = true;
+ var log = document.getElementById("log");
+ var canvas1 = document.getElementById("src-canvas");
+ var canvas2 = document.getElementById("dest-canvas");
+
+ if (!window.chrome || !window.chrome.gpuBenchmarking ||
+ !window.chrome.gpuBenchmarking.beginWindowSnapshotPNG) {
+ canvas1.style.display = "none";
+ canvas2.style.display = "none";
+ log.innerHTML = "chrome.gpuBenchmarking.beginWindowSnapshotPNG not available.<br/>" +
+ "Please make sure Chrome was launched with --enable-gpu-benchmarking."
+ window.__testComplete = true;
+ window.__testMessage = 'chrome.gpuBenchmarking.beginWindowSnapshotPNG not available.';
+ window.__testSuccess = false;
+ return;
+ }
+
+ var totalTests = 100;
+ var testInterval = 75;
+ var testStartDelay = 1000;
+ var testCount = 0;
+
+ var ctx1 = canvas1.getContext("2d");
+ var ctx2 = canvas2.getContext("2d");
+
+ var clearColor = [0, 0, 0];
+ var screenshotClearColor = [0, 0, 0];
+ var validatedColor = [0, 0, 0];
+ var capturing = false;
+ function draw() {
+ if (testCount == totalTests || !window.__testSuccess)
+ return;
+
+ if (!capturing) {
+ clearColor[0] = Math.floor(Math.random() * 256.0);
+ clearColor[1] = Math.floor(Math.random() * 256.0);
+ clearColor[2] = Math.floor(Math.random() * 256.0);
+
+ ctx1.fillStyle="RGB(" +
+ clearColor[0] + "," +
+ clearColor[1] + "," +
+ clearColor[2] + ")";
+ ctx1.fillRect(0,0,canvas1.width,canvas1.height);
+ }
+
+ window.requestAnimationFrame(draw);
+ };
+ draw();
+
+ var snapshotImg = new Image();
+ var snapshotBtn = document.getElementById("snapshot");
+ var snapshotColorSpan = document.getElementById("snapshotColorSpan");
+ var validatedColorSpan = document.getElementById("validatedColorSpan");
+
+
+ function colorsMatch(a, b) {
+ return (Math.abs(a[0] - b[0]) < 2 &&
+ Math.abs(a[1] - b[1]) < 2 &&
+ Math.abs(a[2] - b[2]) < 2);
+ }
+
+ function logString(str) {
+ var entry = document.createElement("div");
+ entry.innerHTML = str
+ log.insertBefore(entry, log.firstChild);
+ }
+
+ function colorToString(a) {
+ return "[" + a[0] +", " + a[1] +", " + a[2] +"]";
+ }
+
+ function logTest(id, a, b) {
+ var match = colorsMatch(a, b);
+ logString("Test " + id + ": " +
+ colorToString(a) + " " +
+ "~= " +
+ colorToString(b) + " " +
+ ": " +
+ (match ? "<b style='color: green'>Pass</b>" : "<b style='color: red'>Fail</b>"));
+ return match;
+ }
+
+ // Take snapshots at an arbitrary interval and ensure that the resulting
+ // image matches the color we last cleared the webgl canvas with
+ function testSnapshot() {
+ capturing = true;
+ ++testCount;
+
+ screenshotClearColor[0] = clearColor[0];
+ screenshotClearColor[1] = clearColor[1];
+ screenshotClearColor[2] = clearColor[2];
+
+ window.chrome.gpuBenchmarking.beginWindowSnapshotPNG(
+ function(s) {
+ snapshotImg.src = "data:image/png;base64," + s.data;
+ ctx2.drawImage(snapshotImg,0,0);
+
+ var img_data = ctx2.getImageData(0, 0, 1, 1);
+ validatedColor[0] = img_data.data[0];
+ validatedColor[1] = img_data.data[1];
+ validatedColor[2] = img_data.data[2];
+
+ window.__testSuccess = window.__testSuccess && logTest(testCount, screenshotClearColor, validatedColor);
+ if (!window.__testSuccess) {
+ log.classList.add("failed");
+ window.__testMessage = 'Color mismatch after ' + testCount + ' iterations.';
+ }
+
+ capturing = false;
+
+ if (testCount < totalTests /*&& window.__testSuccess*/) {
+ if (window.__testSuccess)
+ setTimeout(testSnapshot, testInterval);
+ } else {
+ window.__testComplete = true;
+ }
+ }
+ );
+ }
+ setTimeout(testSnapshot, testStartDelay);
+ })();
+ </script>
+ </body>
+</html>
diff --git a/ui/events/latency_info.h b/ui/events/latency_info.h
index d1e6f75..fb58176 100644
--- a/ui/events/latency_info.h
+++ b/ui/events/latency_info.h
@@ -52,6 +52,9 @@ enum LatencyComponentType {
// Timestamp when the frame is swapped (i.e. when the rendering caused by
// input event actually takes effect).
INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT,
+ // Frame number when a window snapshot was requested. The snapshot
+ // is taken when the rendering results actually reach the screen.
+ WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT
};
struct EVENTS_EXPORT LatencyInfo {