summaryrefslogtreecommitdiffstats
path: root/cc/output
diff options
context:
space:
mode:
authorbrianderson@chromium.org <brianderson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-12 13:21:48 +0000
committerbrianderson@chromium.org <brianderson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-12 13:21:48 +0000
commitbe62216f3ea56d5e9dc9bccf2c37e6e7937de290 (patch)
treef7da60411946679e8666addc93d4fe9a82216aa5 /cc/output
parentff0f4128d79ff878318a0c266947ee5ad14fc662 (diff)
downloadchromium_src-be62216f3ea56d5e9dc9bccf2c37e6e7937de290.zip
chromium_src-be62216f3ea56d5e9dc9bccf2c37e6e7937de290.tar.gz
chromium_src-be62216f3ea56d5e9dc9bccf2c37e6e7937de290.tar.bz2
cc: Emulate BeginFrame in OutputSurfaces that don't support it natively
This will allow us to avoid having two different code paths in the Scheduler. It also allows us to more easily remove the VSyncTimeSource and FrameRateController from the Scheduler. This patch instantiates the FrameRateController inside of OutputSurface for now, but the FrameRateController could be removed in future patches. BUG=245920 BUG=243497 Review URL: https://chromiumcodereview.appspot.com/15836005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@205750 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'cc/output')
-rw-r--r--cc/output/output_surface.cc149
-rw-r--r--cc/output/output_surface.h44
-rw-r--r--cc/output/output_surface_client.h2
-rw-r--r--cc/output/output_surface_unittest.cc159
4 files changed, 311 insertions, 43 deletions
diff --git a/cc/output/output_surface.cc b/cc/output/output_surface.cc
index 0889b96..6efa083 100644
--- a/cc/output/output_surface.cc
+++ b/cc/output/output_surface.cc
@@ -9,12 +9,14 @@
#include <vector>
#include "base/bind.h"
+#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "cc/output/compositor_frame.h"
#include "cc/output/output_surface_client.h"
+#include "cc/scheduler/delay_based_time_source.h"
#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "third_party/khronos/GLES2/gl2ext.h"
@@ -32,7 +34,7 @@ class OutputSurfaceCallbacks
WebGraphicsSwapBuffersCompleteCallbackCHROMIUM,
public WebKit::WebGraphicsContext3D::WebGraphicsContextLostCallback {
public:
- explicit OutputSurfaceCallbacks(OutputSurfaceClient* client)
+ explicit OutputSurfaceCallbacks(OutputSurface* client)
: client_(client) {
DCHECK(client_);
}
@@ -44,50 +46,155 @@ class OutputSurfaceCallbacks
virtual void onContextLost() { client_->DidLoseOutputSurface(); }
private:
- OutputSurfaceClient* client_;
+ OutputSurface* client_;
};
OutputSurface::OutputSurface(
scoped_ptr<WebKit::WebGraphicsContext3D> context3d)
- : client_(NULL),
- context3d_(context3d.Pass()),
+ : context3d_(context3d.Pass()),
has_gl_discard_backbuffer_(false),
has_swap_buffers_complete_callback_(false),
device_scale_factor_(-1),
- weak_ptr_factory_(this) {
+ weak_ptr_factory_(this),
+ max_frames_pending_(0),
+ pending_swap_buffers_(0),
+ begin_frame_pending_(false),
+ client_(NULL) {
}
OutputSurface::OutputSurface(
scoped_ptr<cc::SoftwareOutputDevice> software_device)
- : client_(NULL),
- software_device_(software_device.Pass()),
+ : software_device_(software_device.Pass()),
has_gl_discard_backbuffer_(false),
has_swap_buffers_complete_callback_(false),
device_scale_factor_(-1),
- weak_ptr_factory_(this) {
+ weak_ptr_factory_(this),
+ max_frames_pending_(0),
+ pending_swap_buffers_(0),
+ begin_frame_pending_(false),
+ client_(NULL) {
}
OutputSurface::OutputSurface(
scoped_ptr<WebKit::WebGraphicsContext3D> context3d,
scoped_ptr<cc::SoftwareOutputDevice> software_device)
- : client_(NULL),
- context3d_(context3d.Pass()),
+ : context3d_(context3d.Pass()),
software_device_(software_device.Pass()),
has_gl_discard_backbuffer_(false),
has_swap_buffers_complete_callback_(false),
device_scale_factor_(-1),
- weak_ptr_factory_(this) {
+ weak_ptr_factory_(this),
+ max_frames_pending_(0),
+ pending_swap_buffers_(0),
+ begin_frame_pending_(false),
+ client_(NULL) {
+}
+
+void OutputSurface::InitializeBeginFrameEmulation(
+ Thread* thread,
+ bool throttle_frame_production,
+ base::TimeDelta interval) {
+ if (throttle_frame_production){
+ frame_rate_controller_.reset(
+ new FrameRateController(
+ DelayBasedTimeSource::Create(interval, thread)));
+ } else {
+ frame_rate_controller_.reset(new FrameRateController(thread));
+ }
+
+ frame_rate_controller_->SetClient(this);
+ frame_rate_controller_->SetMaxSwapsPending(max_frames_pending_);
+
+ // The new frame rate controller will consume the swap acks of the old
+ // frame rate controller, so we set that expectation up here.
+ for (int i = 0; i < pending_swap_buffers_; i++)
+ frame_rate_controller_->DidSwapBuffers();
+}
+
+void OutputSurface::SetMaxFramesPending(int max_frames_pending) {
+ if (frame_rate_controller_)
+ frame_rate_controller_->SetMaxSwapsPending(max_frames_pending);
+ max_frames_pending_ = max_frames_pending;
+}
+
+void OutputSurface::OnVSyncParametersChanged(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ TRACE_EVENT2("cc", "OutputSurface::OnVSyncParametersChanged",
+ "timebase", (timebase - base::TimeTicks()).InSecondsF(),
+ "interval", interval.InSecondsF());
+ if (frame_rate_controller_)
+ frame_rate_controller_->SetTimebaseAndInterval(timebase, interval);
+}
+
+void OutputSurface::FrameRateControllerTick(bool throttled) {
+ DCHECK(frame_rate_controller_);
+ if (!throttled)
+ BeginFrame(frame_rate_controller_->LastTickTime());
+}
+
+// Forwarded to OutputSurfaceClient
+void OutputSurface::SetNeedsRedrawRect(gfx::Rect damage_rect) {
+ TRACE_EVENT0("cc", "OutputSurface::SetNeedsRedrawRect");
+ client_->SetNeedsRedrawRect(damage_rect);
+}
+
+void OutputSurface::SetNeedsBeginFrame(bool enable) {
+ TRACE_EVENT1("cc", "OutputSurface::SetNeedsBeginFrame", "enable", enable);
+ begin_frame_pending_ = false;
+ if (frame_rate_controller_)
+ frame_rate_controller_->SetActive(enable);
+}
+
+void OutputSurface::BeginFrame(base::TimeTicks frame_time) {
+ if (begin_frame_pending_ ||
+ (pending_swap_buffers_ >= max_frames_pending_ && max_frames_pending_ > 0))
+ return;
+ TRACE_EVENT1("cc", "OutputSurface::BeginFrame",
+ "pending_swap_buffers_", pending_swap_buffers_);
+ begin_frame_pending_ = true;
+ client_->BeginFrame(frame_time);
+}
+
+void OutputSurface::DidSwapBuffers() {
+ begin_frame_pending_ = false;
+ pending_swap_buffers_++;
+ TRACE_EVENT1("cc", "OutputSurface::DidSwapBuffers",
+ "pending_swap_buffers_", pending_swap_buffers_);
+ if (frame_rate_controller_)
+ frame_rate_controller_->DidSwapBuffers();
+}
+
+void OutputSurface::OnSwapBuffersComplete(const CompositorFrameAck* ack) {
+ pending_swap_buffers_--;
+ TRACE_EVENT1("cc", "OutputSurface::OnSwapBuffersComplete",
+ "pending_swap_buffers_", pending_swap_buffers_);
+ client_->OnSwapBuffersComplete(ack);
+ if (frame_rate_controller_)
+ frame_rate_controller_->DidSwapBuffersComplete();
+}
+
+void OutputSurface::DidLoseOutputSurface() {
+ TRACE_EVENT0("cc", "OutputSurface::DidLoseOutputSurface");
+ begin_frame_pending_ = false;
+ pending_swap_buffers_ = 0;
+ client_->DidLoseOutputSurface();
+}
+
+void OutputSurface::SetExternalDrawConstraints(const gfx::Transform& transform,
+ gfx::Rect viewport) {
+ client_->SetExternalDrawConstraints(transform, viewport);
}
OutputSurface::~OutputSurface() {
+ if (frame_rate_controller_)
+ frame_rate_controller_->SetActive(false);
}
bool OutputSurface::ForcedDrawToSoftwareDevice() const {
return false;
}
-bool OutputSurface::BindToClient(
- cc::OutputSurfaceClient* client) {
+bool OutputSurface::BindToClient(cc::OutputSurfaceClient* client) {
DCHECK(client);
client_ = client;
bool success = true;
@@ -143,7 +250,7 @@ void OutputSurface::SetContext3D(
context3d_ = context3d.Pass();
- callbacks_.reset(new OutputSurfaceCallbacks(client_));
+ callbacks_.reset(new OutputSurfaceCallbacks(this));
context3d_->setSwapBuffersCompleteCallbackCHROMIUM(callbacks_.get());
context3d_->setContextLostCallback(callbacks_.get());
}
@@ -206,20 +313,16 @@ void OutputSurface::SwapBuffers(cc::CompositorFrame* frame) {
if (!has_swap_buffers_complete_callback_)
PostSwapBuffersComplete();
+
+ DidSwapBuffers();
}
void OutputSurface::PostSwapBuffersComplete() {
base::MessageLoop::current()->PostTask(
FROM_HERE,
- base::Bind(&OutputSurface::SwapBuffersComplete,
- weak_ptr_factory_.GetWeakPtr()));
-}
-
-void OutputSurface::SwapBuffersComplete() {
- if (!client_)
- return;
-
- client_->OnSwapBuffersComplete(NULL);
+ base::Bind(&OutputSurface::OnSwapBuffersComplete,
+ weak_ptr_factory_.GetWeakPtr(),
+ static_cast<CompositorFrameAck*>(NULL)));
}
} // namespace cc
diff --git a/cc/output/output_surface.h b/cc/output/output_surface.h
index d263d3a..8f782d7 100644
--- a/cc/output/output_surface.h
+++ b/cc/output/output_surface.h
@@ -12,6 +12,7 @@
#include "cc/base/cc_export.h"
#include "cc/output/context_provider.h"
#include "cc/output/software_output_device.h"
+#include "cc/scheduler/frame_rate_controller.h"
#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h"
namespace ui { struct LatencyInfo; }
@@ -19,13 +20,16 @@ namespace ui { struct LatencyInfo; }
namespace gfx {
class Rect;
class Size;
+class Transform;
}
namespace cc {
class CompositorFrame;
+class CompositorFrameAck;
class OutputSurfaceClient;
class OutputSurfaceCallbacks;
+class Thread;
// Represents the output surface for a compositor. The compositor owns
// and manages its destruction. Its lifetime is:
@@ -34,7 +38,7 @@ class OutputSurfaceCallbacks;
// From here on, it will only be used on the compositor thread.
// 3. If the 3D context is lost, then the compositor will delete the output
// surface (on the compositor thread) and go back to step 1.
-class CC_EXPORT OutputSurface {
+class CC_EXPORT OutputSurface : public FrameRateControllerClient {
public:
explicit OutputSurface(scoped_ptr<WebKit::WebGraphicsContext3D> context3d);
@@ -83,6 +87,13 @@ class CC_EXPORT OutputSurface {
// thread.
virtual bool BindToClient(OutputSurfaceClient* client);
+ void InitializeBeginFrameEmulation(
+ Thread* thread,
+ bool throttle_frame_production,
+ base::TimeDelta interval);
+
+ void SetMaxFramesPending(int max_frames_pending);
+
virtual void EnsureBackbuffer();
virtual void DiscardBackbuffer();
@@ -103,7 +114,7 @@ class CC_EXPORT OutputSurface {
// Requests a BeginFrame notification from the output surface. The
// notification will be delivered by calling
// OutputSurfaceClient::BeginFrame until the callback is disabled.
- virtual void SetNeedsBeginFrame(bool enable) {}
+ virtual void SetNeedsBeginFrame(bool enable);
protected:
// Synchronously initialize context3d and enter hardware mode.
@@ -116,7 +127,6 @@ class CC_EXPORT OutputSurface {
void PostSwapBuffersComplete();
- OutputSurfaceClient* client_;
struct cc::OutputSurface::Capabilities capabilities_;
scoped_ptr<OutputSurfaceCallbacks> callbacks_;
scoped_ptr<WebKit::WebGraphicsContext3D> context3d_;
@@ -125,12 +135,34 @@ class CC_EXPORT OutputSurface {
bool has_swap_buffers_complete_callback_;
gfx::Size surface_size_;
float device_scale_factor_;
+ base::WeakPtrFactory<OutputSurface> weak_ptr_factory_;
+
+ // The FrameRateController is deprecated.
+ // Platforms should move to native BeginFrames instead.
+ void OnVSyncParametersChanged(base::TimeTicks timebase,
+ base::TimeDelta interval);
+ virtual void FrameRateControllerTick(bool throttled) OVERRIDE;
+ scoped_ptr<FrameRateController> frame_rate_controller_;
+ int max_frames_pending_;
+ int pending_swap_buffers_;
+ bool begin_frame_pending_;
+
+ // Forwarded to OutputSurfaceClient but threaded through OutputSurface
+ // first so OutputSurface has a chance to update the FrameRateController
+ bool HasClient() { return !!client_; }
+ void SetNeedsRedrawRect(gfx::Rect damage_rect);
+ void BeginFrame(base::TimeTicks frame_time);
+ void DidSwapBuffers();
+ void OnSwapBuffersComplete(const CompositorFrameAck* ack);
+ void DidLoseOutputSurface();
+ void SetExternalDrawConstraints(const gfx::Transform& transform,
+ gfx::Rect viewport);
private:
- void SetContext3D(scoped_ptr<WebKit::WebGraphicsContext3D> context3d);
- void SwapBuffersComplete();
+ OutputSurfaceClient* client_;
+ friend class OutputSurfaceCallbacks;
- base::WeakPtrFactory<OutputSurface> weak_ptr_factory_;
+ void SetContext3D(scoped_ptr<WebKit::WebGraphicsContext3D> context3d);
DISALLOW_COPY_AND_ASSIGN(OutputSurface);
};
diff --git a/cc/output/output_surface_client.h b/cc/output/output_surface_client.h
index f5999c3..d727629 100644
--- a/cc/output/output_surface_client.h
+++ b/cc/output/output_surface_client.h
@@ -27,8 +27,6 @@ class CC_EXPORT OutputSurfaceClient {
virtual bool DeferredInitialize(
scoped_refptr<ContextProvider> offscreen_context_provider) = 0;
virtual void SetNeedsRedrawRect(gfx::Rect damage_rect) = 0;
- virtual void OnVSyncParametersChanged(base::TimeTicks timebase,
- base::TimeDelta interval) = 0;
virtual void BeginFrame(base::TimeTicks frame_time) = 0;
virtual void OnSwapBuffersComplete(const CompositorFrameAck* ack) = 0;
virtual void DidLoseOutputSurface() = 0;
diff --git a/cc/output/output_surface_unittest.cc b/cc/output/output_surface_unittest.cc
index ebc018f..7dcb1cc 100644
--- a/cc/output/output_surface_unittest.cc
+++ b/cc/output/output_surface_unittest.cc
@@ -5,6 +5,7 @@
#include "cc/output/output_surface.h"
#include "cc/output/output_surface_client.h"
#include "cc/output/software_output_device.h"
+#include "cc/test/scheduler_test_common.h"
#include "cc/test/test_web_graphics_context_3d.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -25,19 +26,43 @@ class TestOutputSurface : public OutputSurface {
scoped_ptr<cc::SoftwareOutputDevice> software_device)
: OutputSurface(context3d.Pass(), software_device.Pass()) {}
- OutputSurfaceClient* client() { return client_; }
-
bool InitializeNewContext3D(
scoped_ptr<WebKit::WebGraphicsContext3D> new_context3d) {
return InitializeAndSetContext3D(new_context3d.Pass(),
scoped_refptr<ContextProvider>());
}
+
+ bool HasClientForTesting() {
+ return HasClient();
+ }
+
+ void OnVSyncParametersChangedForTesting(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ OnVSyncParametersChanged(timebase, interval);
+ }
+
+ void BeginFrameForTesting(base::TimeTicks frame_time) {
+ BeginFrame(frame_time);
+ }
+
+ void DidSwapBuffersForTesting() {
+ DidSwapBuffers();
+ }
+
+ int pending_swap_buffers() {
+ return pending_swap_buffers_;
+ }
+
+ void OnSwapBuffersCompleteForTesting() {
+ OnSwapBuffersComplete(NULL);
+ }
};
class FakeOutputSurfaceClient : public OutputSurfaceClient {
public:
FakeOutputSurfaceClient()
- : deferred_initialize_result_(true),
+ : begin_frame_count_(0),
+ deferred_initialize_result_(true),
deferred_initialize_called_(false),
did_lose_output_surface_called_(false) {}
@@ -47,9 +72,9 @@ class FakeOutputSurfaceClient : public OutputSurfaceClient {
return deferred_initialize_result_;
}
virtual void SetNeedsRedrawRect(gfx::Rect damage_rect) OVERRIDE {}
- virtual void OnVSyncParametersChanged(base::TimeTicks timebase,
- base::TimeDelta interval) OVERRIDE {}
- virtual void BeginFrame(base::TimeTicks frame_time) OVERRIDE {}
+ virtual void BeginFrame(base::TimeTicks frame_time) OVERRIDE {
+ begin_frame_count_++;
+ }
virtual void OnSwapBuffersComplete(const CompositorFrameAck* ack) OVERRIDE {}
virtual void DidLoseOutputSurface() OVERRIDE {
did_lose_output_surface_called_ = true;
@@ -57,6 +82,10 @@ class FakeOutputSurfaceClient : public OutputSurfaceClient {
virtual void SetExternalDrawConstraints(const gfx::Transform& transform,
gfx::Rect viewport) OVERRIDE {}
+ int begin_frame_count() {
+ return begin_frame_count_;
+ }
+
void set_deferred_initialize_result(bool result) {
deferred_initialize_result_ = result;
}
@@ -70,6 +99,7 @@ class FakeOutputSurfaceClient : public OutputSurfaceClient {
}
private:
+ int begin_frame_count_;
bool deferred_initialize_result_;
bool deferred_initialize_called_;
bool did_lose_output_surface_called_;
@@ -81,11 +111,11 @@ TEST(OutputSurfaceTest, ClientPointerIndicatesBindToClientSuccess) {
TestOutputSurface output_surface(
context3d.PassAs<WebKit::WebGraphicsContext3D>());
- EXPECT_EQ(NULL, output_surface.client());
+ EXPECT_FALSE(output_surface.HasClientForTesting());
FakeOutputSurfaceClient client;
EXPECT_TRUE(output_surface.BindToClient(&client));
- EXPECT_EQ(&client, output_surface.client());
+ EXPECT_TRUE(output_surface.HasClientForTesting());
EXPECT_FALSE(client.deferred_initialize_called());
// Verify DidLoseOutputSurface callback is hooked up correctly.
@@ -104,11 +134,11 @@ TEST(OutputSurfaceTest, ClientPointerIndicatesBindToClientFailure) {
TestOutputSurface output_surface(
context3d.PassAs<WebKit::WebGraphicsContext3D>());
- EXPECT_EQ(NULL, output_surface.client());
+ EXPECT_FALSE(output_surface.HasClientForTesting());
FakeOutputSurfaceClient client;
EXPECT_FALSE(output_surface.BindToClient(&client));
- EXPECT_EQ(NULL, output_surface.client());
+ EXPECT_FALSE(output_surface.HasClientForTesting());
}
class InitializeNewContext3D : public ::testing::Test {
@@ -121,13 +151,13 @@ class InitializeNewContext3D : public ::testing::Test {
protected:
void BindOutputSurface() {
EXPECT_TRUE(output_surface_.BindToClient(&client_));
- EXPECT_EQ(&client_, output_surface_.client());
+ EXPECT_TRUE(output_surface_.HasClientForTesting());
}
void InitializeNewContextExpectFail() {
EXPECT_FALSE(output_surface_.InitializeNewContext3D(
context3d_.PassAs<WebKit::WebGraphicsContext3D>()));
- EXPECT_EQ(&client_, output_surface_.client());
+ EXPECT_TRUE(output_surface_.HasClientForTesting());
EXPECT_FALSE(output_surface_.context3d());
EXPECT_TRUE(output_surface_.software_device());
@@ -164,5 +194,110 @@ TEST_F(InitializeNewContext3D, ClientDeferredInitializeFails) {
InitializeNewContextExpectFail();
}
+TEST(OutputSurfaceTest, BeginFrameEmulation) {
+ scoped_ptr<TestWebGraphicsContext3D> context3d =
+ TestWebGraphicsContext3D::Create();
+
+ TestOutputSurface output_surface(
+ context3d.PassAs<WebKit::WebGraphicsContext3D>());
+ EXPECT_FALSE(output_surface.HasClientForTesting());
+
+ FakeOutputSurfaceClient client;
+ EXPECT_TRUE(output_surface.BindToClient(&client));
+ EXPECT_TRUE(output_surface.HasClientForTesting());
+ EXPECT_FALSE(client.deferred_initialize_called());
+
+ // Initialize BeginFrame emulation
+ FakeThread impl_thread;
+ bool throttle_frame_production = true;
+ const base::TimeDelta display_refresh_interval =
+ base::TimeDelta::FromMicroseconds(16666);
+
+ output_surface.InitializeBeginFrameEmulation(
+ &impl_thread,
+ throttle_frame_production,
+ display_refresh_interval);
+
+ output_surface.SetMaxFramesPending(2);
+
+ // We should start off with 0 BeginFrames
+ EXPECT_EQ(client.begin_frame_count(), 0);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 0);
+
+ // We should not have a pending task until a BeginFrame has been requested.
+ EXPECT_FALSE(impl_thread.HasPendingTask());
+ output_surface.SetNeedsBeginFrame(true);
+ EXPECT_TRUE(impl_thread.HasPendingTask());
+
+ // BeginFrame should be called on the first tick.
+ impl_thread.RunPendingTask();
+ EXPECT_EQ(client.begin_frame_count(), 1);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 0);
+
+ // BeginFrame should not be called when there is a pending BeginFrame.
+ impl_thread.RunPendingTask();
+ EXPECT_EQ(client.begin_frame_count(), 1);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 0);
+
+ // DidSwapBuffers should clear the pending BeginFrame.
+ output_surface.DidSwapBuffersForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 1);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+ impl_thread.RunPendingTask();
+ EXPECT_EQ(client.begin_frame_count(), 2);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+
+ // BeginFrame should be throttled by pending swap buffers.
+ output_surface.DidSwapBuffersForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 2);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 2);
+ impl_thread.RunPendingTask();
+ EXPECT_EQ(client.begin_frame_count(), 2);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 2);
+
+ // SwapAck should decrement pending swap buffers and unblock BeginFrame again.
+ output_surface.OnSwapBuffersCompleteForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 2);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+ impl_thread.RunPendingTask();
+ EXPECT_EQ(client.begin_frame_count(), 3);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+
+ // Calling SetNeedsBeginFrame again indicates a swap did not occur but
+ // the client still wants another BeginFrame.
+ output_surface.SetNeedsBeginFrame(true);
+ impl_thread.RunPendingTask();
+ EXPECT_EQ(client.begin_frame_count(), 4);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+
+ // Disabling SetNeedsBeginFrame should prevent further BeginFrames.
+ output_surface.SetNeedsBeginFrame(false);
+ impl_thread.RunPendingTask();
+ EXPECT_FALSE(impl_thread.HasPendingTask());
+ EXPECT_EQ(client.begin_frame_count(), 4);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+
+ // Optimistically injected BeginFrames without a SetNeedsBeginFrame should be
+ // allowed.
+ output_surface.BeginFrameForTesting(base::TimeTicks::Now());
+ EXPECT_EQ(client.begin_frame_count(), 5);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+
+ // Optimistically injected BeginFrames without a SetNeedsBeginFrame should
+ // still be throttled by pending begin frames however.
+ output_surface.BeginFrameForTesting(base::TimeTicks::Now());
+ EXPECT_EQ(client.begin_frame_count(), 5);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 1);
+
+ // Optimistically injected BeginFrames without a SetNeedsBeginFrame should
+ // also be throttled by pending swap buffers.
+ output_surface.DidSwapBuffersForTesting();
+ EXPECT_EQ(client.begin_frame_count(), 5);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 2);
+ output_surface.BeginFrameForTesting(base::TimeTicks::Now());
+ EXPECT_EQ(client.begin_frame_count(), 5);
+ EXPECT_EQ(output_surface.pending_swap_buffers(), 2);
+}
+
} // namespace
} // namespace cc