blob: 54b5cc6d2daf66fd8dd5416966b7ff04a2ca4958 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/graphics/video_frame_submitter.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/test/bind.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "cc/layers/video_frame_provider.h"
#include "cc/metrics/video_playback_roughness_reporter.h"
#include "cc/test/layer_test_common.h"
#include "cc/trees/layer_tree_settings.h"
#include "cc/trees/task_runner_provider.h"
#include "components/viz/test/fake_external_begin_frame_source.h"
#include "components/viz/test/test_context_provider.h"
#include "media/base/video_frame.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/viz/public/mojom/compositing/compositor_frame_sink.mojom-blink.h"
#include "services/viz/public/mojom/hit_test/hit_test_region_list.mojom-blink.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h"
#include "third_party/blink/renderer/platform/graphics/test/mock_embedded_frame_sink_provider.h"
#include "third_party/blink/renderer/platform/graphics/video_frame_resource_provider.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
using testing::_;
using testing::AnyNumber;
using testing::Invoke;
using testing::Return;
using testing::StrictMock;
namespace blink {
namespace {
class MockVideoFrameProvider : public cc::VideoFrameProvider {
public:
MockVideoFrameProvider() = default;
~MockVideoFrameProvider() override = default;
MOCK_METHOD1(SetVideoFrameProviderClient, void(Client*));
MOCK_METHOD2(UpdateCurrentFrame, bool(base::TimeTicks, base::TimeTicks));
MOCK_METHOD0(HasCurrentFrame, bool());
MOCK_METHOD0(GetCurrentFrame, scoped_refptr<media::VideoFrame>());
MOCK_METHOD0(PutCurrentFrame, void());
base::TimeDelta GetPreferredRenderInterval() override {
return preferred_interval;
}
base::TimeDelta preferred_interval;
private:
DISALLOW_COPY_AND_ASSIGN(MockVideoFrameProvider);
};
class VideoMockCompositorFrameSink
: public viz::mojom::blink::CompositorFrameSink {
public:
VideoMockCompositorFrameSink(
mojo::PendingReceiver<viz::mojom::blink::CompositorFrameSink> receiver) {
receiver_.Bind(std::move(receiver));
}
~VideoMockCompositorFrameSink() override = default;
const viz::CompositorFrame& last_submitted_compositor_frame() const {
return last_submitted_compositor_frame_;
}
MOCK_METHOD1(SetNeedsBeginFrame, void(bool));
MOCK_METHOD0(SetWantsAnimateOnlyBeginFrames, void());
MOCK_METHOD2(DoSubmitCompositorFrame,
void(const viz::LocalSurfaceId&, viz::CompositorFrame*));
void SubmitCompositorFrame(
const viz::LocalSurfaceId& id,
viz::CompositorFrame frame,
base::Optional<viz::HitTestRegionList> hit_test_region_list,
uint64_t submit_time) override {
last_submitted_compositor_frame_ = std::move(frame);
DoSubmitCompositorFrame(id, &last_submitted_compositor_frame_);
}
void SubmitCompositorFrameSync(
const viz::LocalSurfaceId& id,
viz::CompositorFrame frame,
base::Optional<viz::HitTestRegionList> hit_test_region_list,
uint64_t submit_time,
const SubmitCompositorFrameSyncCallback callback) override {
last_submitted_compositor_frame_ = std::move(frame);
DoSubmitCompositorFrame(id, &last_submitted_compositor_frame_);
}
MOCK_METHOD1(DidNotProduceFrame, void(const viz::BeginFrameAck&));
MOCK_METHOD2(DidAllocateSharedBitmap,
void(base::ReadOnlySharedMemoryRegion region,
const gpu::Mailbox& id));
MOCK_METHOD1(DidDeleteSharedBitmap, void(const gpu::Mailbox& id));
MOCK_METHOD1(InitializeCompositorFrameSinkType,
void(viz::mojom::CompositorFrameSinkType));
private:
mojo::Receiver<viz::mojom::blink::CompositorFrameSink> receiver_{this};
viz::CompositorFrame last_submitted_compositor_frame_;
DISALLOW_COPY_AND_ASSIGN(VideoMockCompositorFrameSink);
};
class MockVideoFrameResourceProvider
: public blink::VideoFrameResourceProvider {
public:
MockVideoFrameResourceProvider(
viz::RasterContextProvider* context_provider,
viz::SharedBitmapReporter* shared_bitmap_reporter)
: blink::VideoFrameResourceProvider(cc::LayerTreeSettings(), false) {
blink::VideoFrameResourceProvider::Initialize(context_provider,
shared_bitmap_reporter);
}
~MockVideoFrameResourceProvider() override = default;
MOCK_METHOD2(Initialize,
void(viz::RasterContextProvider*, viz::SharedBitmapReporter*));
MOCK_METHOD4(AppendQuads,
void(viz::CompositorRenderPass*,
scoped_refptr<media::VideoFrame>,
media::VideoRotation,
bool));
MOCK_METHOD0(ReleaseFrameResources, void());
MOCK_METHOD2(PrepareSendToParent,
void(const WebVector<viz::ResourceId>&,
WebVector<viz::TransferableResource>*));
MOCK_METHOD1(
ReceiveReturnsFromParent,
void(const Vector<viz::ReturnedResource>& transferable_resources));
MOCK_METHOD0(ObtainContextProvider, void());
private:
DISALLOW_COPY_AND_ASSIGN(MockVideoFrameResourceProvider);
};
} // namespace
class VideoFrameSubmitterTest : public testing::Test {
public:
VideoFrameSubmitterTest()
: now_src_(new base::SimpleTestTickClock()),
begin_frame_source_(new viz::FakeExternalBeginFrameSource(0.f, false)),
video_frame_provider_(new StrictMock<MockVideoFrameProvider>()),
context_provider_(viz::TestContextProvider::Create()) {
context_provider_->BindToCurrentThread();
MakeSubmitter();
task_environment_.RunUntilIdle();
}
void MakeSubmitter() { MakeSubmitter(base::DoNothing()); }
void MakeSubmitter(
cc::VideoPlaybackRoughnessReporter::ReportingCallback reporting_cb) {
resource_provider_ = new StrictMock<MockVideoFrameResourceProvider>(
context_provider_.get(), nullptr);
submitter_ = std::make_unique<VideoFrameSubmitter>(
base::DoNothing(), reporting_cb,
base::WrapUnique<MockVideoFrameResourceProvider>(resource_provider_));
submitter_->Initialize(video_frame_provider_.get(), false);
mojo::PendingRemote<viz::mojom::blink::CompositorFrameSink> submitter_sink;
sink_ = std::make_unique<StrictMock<VideoMockCompositorFrameSink>>(
submitter_sink.InitWithNewPipeAndPassReceiver());
// By setting the submission state before we set the sink, we can make
// testing easier without having to worry about the first sent frame.
submitter_->SetIsSurfaceVisible(true);
submitter_->compositor_frame_sink_.Bind(std::move(submitter_sink));
mojo::Remote<mojom::blink::SurfaceEmbedder> embedder;
ignore_result(embedder.BindNewPipeAndPassReceiver());
submitter_->surface_embedder_ = std::move(embedder);
auto surface_id = viz::SurfaceId(
viz::FrameSinkId(1, 1),
viz::LocalSurfaceId(11,
base::UnguessableToken::Deserialize(0x111111, 0)));
submitter_->frame_sink_id_ = surface_id.frame_sink_id();
submitter_->child_local_surface_id_allocator_.UpdateFromParent(
surface_id.local_surface_id());
}
bool IsRendering() const { return submitter_->is_rendering_; }
cc::VideoFrameProvider* GetProvider() const {
return submitter_->video_frame_provider_;
}
bool ShouldSubmit() const { return submitter_->ShouldSubmit(); }
void SubmitSingleFrame() { submitter_->SubmitSingleFrame(); }
const viz::ChildLocalSurfaceIdAllocator& child_local_surface_id_allocator()
const {
return submitter_->child_local_surface_id_allocator_;
}
gfx::Size frame_size() const { return submitter_->frame_size_; }
void OnReceivedContextProvider(
bool use_gpu_compositing,
scoped_refptr<viz::RasterContextProvider> context_provider) {
submitter_->OnReceivedContextProvider(use_gpu_compositing,
std::move(context_provider));
}
void AckSubmittedFrame() {
WTF::Vector<viz::ReturnedResource> resources;
EXPECT_CALL(*resource_provider_, ReceiveReturnsFromParent(_));
submitter_->DidReceiveCompositorFrameAck(resources);
}
protected:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<base::SimpleTestTickClock> now_src_;
std::unique_ptr<viz::FakeExternalBeginFrameSource> begin_frame_source_;
std::unique_ptr<StrictMock<VideoMockCompositorFrameSink>> sink_;
std::unique_ptr<StrictMock<MockVideoFrameProvider>> video_frame_provider_;
StrictMock<MockVideoFrameResourceProvider>* resource_provider_;
scoped_refptr<viz::TestContextProvider> context_provider_;
std::unique_ptr<VideoFrameSubmitter> submitter_;
};
enum class SubmissionType {
kBeginFrame,
kStateChange,
kManual,
};
#define EXPECT_GET_PUT_FRAME() \
do { \
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame()) \
.WillOnce(Return(media::VideoFrame::CreateFrame( \
media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), \
gfx::Rect(gfx::Size(8, 8)), gfx::Size(8, 8), base::TimeDelta()))); \
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame()); \
} while (0)
// Create submission state macro to ease complexity. Use a macro instead of a
// function so that line numbers are useful in test failures.
#define EXPECT_SUBMISSION(type) \
do { \
if (type == SubmissionType::kBeginFrame) { \
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _)) \
.WillOnce(Return(true)); \
} \
EXPECT_GET_PUT_FRAME(); \
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _)); \
EXPECT_CALL(*resource_provider_, AppendQuads(_, _, _, _)); \
EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _)); \
EXPECT_CALL(*resource_provider_, ReleaseFrameResources()); \
} while (0)
TEST_F(VideoFrameSubmitterTest, StatRenderingFlipsBits) {
EXPECT_FALSE(IsRendering());
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_TRUE(IsRendering());
}
TEST_F(VideoFrameSubmitterTest, StopRenderingSkipsUpdateCurrentFrame) {
EXPECT_FALSE(IsRendering());
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_TRUE(IsRendering());
// OnBeginFrame() submits one frame.
EXPECT_SUBMISSION(SubmissionType::kBeginFrame);
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
AckSubmittedFrame();
// StopRendering submits one more frame.
EXPECT_SUBMISSION(SubmissionType::kStateChange);
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
submitter_->StopRendering();
task_environment_.RunUntilIdle();
AckSubmittedFrame();
// No frames should be produced after StopRendering().
EXPECT_CALL(*sink_, DidNotProduceFrame(_));
args = begin_frame_source_->CreateBeginFrameArgs(BEGINFRAME_FROM_HERE,
now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, StopUsingProviderNullsProvider) {
EXPECT_FALSE(IsRendering());
EXPECT_EQ(video_frame_provider_.get(), GetProvider());
submitter_->StopUsingProvider();
EXPECT_EQ(nullptr, GetProvider());
}
TEST_F(VideoFrameSubmitterTest,
StopUsingProviderSubmitsFrameAndStopsRendering) {
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_TRUE(IsRendering());
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
EXPECT_SUBMISSION(SubmissionType::kStateChange);
submitter_->StopUsingProvider();
task_environment_.RunUntilIdle();
EXPECT_FALSE(IsRendering());
}
TEST_F(VideoFrameSubmitterTest, DidReceiveFrameStillSubmitsIfRendering) {
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_TRUE(IsRendering());
EXPECT_SUBMISSION(SubmissionType::kManual);
submitter_->DidReceiveFrame();
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, DidReceiveFrameSubmitsFrame) {
EXPECT_FALSE(IsRendering());
EXPECT_SUBMISSION(SubmissionType::kManual);
submitter_->DidReceiveFrame();
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, ShouldSubmitPreventsSubmission) {
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
submitter_->SetIsSurfaceVisible(false);
task_environment_.RunUntilIdle();
EXPECT_FALSE(ShouldSubmit());
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_SUBMISSION(SubmissionType::kStateChange);
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->SetIsSurfaceVisible(true);
task_environment_.RunUntilIdle();
AckSubmittedFrame();
EXPECT_TRUE(ShouldSubmit());
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame()).Times(0);
submitter_->SetIsSurfaceVisible(false);
task_environment_.RunUntilIdle();
EXPECT_FALSE(ShouldSubmit());
// We should only see a GetCurrentFrame() without a PutCurrentFrame() since
// we drop the submission because !ShouldSubmit().
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
.WillOnce(Return(media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
gfx::Size(8, 8), base::TimeDelta())));
SubmitSingleFrame();
}
// Tests that when set to true SetForceSubmit forces frame submissions.
// regardless of the internal submit state.
TEST_F(VideoFrameSubmitterTest, SetForceSubmitForcesSubmission) {
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
submitter_->SetIsSurfaceVisible(false);
task_environment_.RunUntilIdle();
EXPECT_FALSE(ShouldSubmit());
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
EXPECT_SUBMISSION(SubmissionType::kStateChange);
submitter_->SetForceSubmit(true);
AckSubmittedFrame();
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
EXPECT_SUBMISSION(SubmissionType::kStateChange);
submitter_->SetIsSurfaceVisible(true);
task_environment_.RunUntilIdle();
EXPECT_TRUE(ShouldSubmit());
AckSubmittedFrame();
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
EXPECT_SUBMISSION(SubmissionType::kStateChange);
submitter_->SetIsSurfaceVisible(false);
task_environment_.RunUntilIdle();
EXPECT_TRUE(ShouldSubmit());
AckSubmittedFrame();
EXPECT_SUBMISSION(SubmissionType::kManual);
SubmitSingleFrame();
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, RotationInformationPassedToResourceProvider) {
// Check to see if rotation is communicated pre-rendering.
EXPECT_FALSE(IsRendering());
submitter_->SetRotation(media::VideoRotation::VIDEO_ROTATION_90);
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
.WillOnce(Return(media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
gfx::Size(8, 8), base::TimeDelta())));
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
EXPECT_CALL(*resource_provider_,
AppendQuads(_, _, media::VideoRotation::VIDEO_ROTATION_90, _));
EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
submitter_->DidReceiveFrame();
task_environment_.RunUntilIdle();
AckSubmittedFrame();
// Check to see if an update to rotation just before rendering is
// communicated.
submitter_->SetRotation(media::VideoRotation::VIDEO_ROTATION_180);
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
AckSubmittedFrame();
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
.WillOnce(Return(true));
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
.WillOnce(Return(media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
gfx::Size(8, 8), base::TimeDelta())));
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
EXPECT_CALL(*resource_provider_,
AppendQuads(_, _, media::VideoRotation::VIDEO_ROTATION_180, _));
EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
AckSubmittedFrame();
// Check to see if changing rotation while rendering is handled.
submitter_->SetRotation(media::VideoRotation::VIDEO_ROTATION_270);
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
.WillOnce(Return(true));
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
.WillOnce(Return(media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
gfx::Size(8, 8), base::TimeDelta())));
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
EXPECT_CALL(*resource_provider_,
AppendQuads(_, _, media::VideoRotation::VIDEO_ROTATION_270, _));
EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
args = begin_frame_source_->CreateBeginFrameArgs(BEGINFRAME_FROM_HERE,
now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, OnBeginFrameSubmitsFrame) {
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_SUBMISSION(SubmissionType::kBeginFrame);
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, MissedFrameArgDoesNotProduceFrame) {
EXPECT_CALL(*sink_, DidNotProduceFrame(_));
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
args.type = viz::BeginFrameArgs::MISSED;
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, MissingProviderDoesNotProduceFrame) {
submitter_->StopUsingProvider();
EXPECT_CALL(*sink_, DidNotProduceFrame(_));
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, NoUpdateOnFrameDoesNotProduceFrame) {
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
.WillOnce(Return(false));
EXPECT_CALL(*sink_, DidNotProduceFrame(_));
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, NotRenderingDoesNotProduceFrame) {
// We don't care if UpdateCurrentFrame is called or not; it doesn't matter
// if we're not rendering.
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
.Times(AnyNumber());
EXPECT_CALL(*sink_, DidNotProduceFrame(_));
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, ReturnsResourceOnCompositorAck) {
AckSubmittedFrame();
task_environment_.RunUntilIdle();
}
// Tests that after submitting a frame, no frame will be submitted until an ACK
// was received. This is tested by simulating another BeginFrame message.
TEST_F(VideoFrameSubmitterTest, WaitingForAckPreventsNewFrame) {
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_SUBMISSION(SubmissionType::kBeginFrame);
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
// DidNotProduceFrame should be called because no frame will be submitted
// given that the ACK is still pending.
EXPECT_CALL(*sink_, DidNotProduceFrame(_)).Times(1);
// UpdateCurrentFrame should still be called, however, so that the compositor
// knows that we missed a frame.
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _)).Times(1);
std::unique_ptr<base::SimpleTestTickClock> new_time =
std::make_unique<base::SimpleTestTickClock>();
args = begin_frame_source_->CreateBeginFrameArgs(BEGINFRAME_FROM_HERE,
new_time.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
}
// Similar to above but verifies the single-frame paint path.
TEST_F(VideoFrameSubmitterTest, WaitingForAckPreventsSubmitSingleFrame) {
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_SUBMISSION(SubmissionType::kManual);
submitter_->DidReceiveFrame();
task_environment_.RunUntilIdle();
// GetCurrentFrame() should be called, but PutCurrentFrame() should not, since
// the frame is dropped waiting for the ack.
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
.WillOnce(Return(media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
gfx::Size(8, 8), base::TimeDelta())));
submitter_->DidReceiveFrame();
task_environment_.RunUntilIdle();
}
// Test that after context is lost, the CompositorFrameSink is recreated but the
// SurfaceEmbedder isn't.
TEST_F(VideoFrameSubmitterTest, RecreateCompositorFrameSinkAfterContextLost) {
MockEmbeddedFrameSinkProvider mock_embedded_frame_sink_provider;
mojo::Receiver<mojom::blink::EmbeddedFrameSinkProvider>
embedded_frame_sink_provider_binding(&mock_embedded_frame_sink_provider);
auto override =
mock_embedded_frame_sink_provider.CreateScopedOverrideMojoInterface(
&embedded_frame_sink_provider_binding);
EXPECT_CALL(*resource_provider_, Initialize(_, _));
EXPECT_CALL(mock_embedded_frame_sink_provider, ConnectToEmbedder(_, _))
.Times(0);
EXPECT_CALL(mock_embedded_frame_sink_provider, CreateCompositorFrameSink_(_))
.Times(1);
submitter_->OnContextLost();
OnReceivedContextProvider(true, context_provider_);
task_environment_.RunUntilIdle();
}
// Test that after context is lost, the CompositorFrameSink is recreated but the
// SurfaceEmbedder isn't even with software compositing.
TEST_F(VideoFrameSubmitterTest,
RecreateCompositorFrameSinkAfterContextLostSoftwareCompositing) {
MockEmbeddedFrameSinkProvider mock_embedded_frame_sink_provider;
mojo::Receiver<mojom::blink::EmbeddedFrameSinkProvider>
embedded_frame_sink_provider_binding(&mock_embedded_frame_sink_provider);
auto override =
mock_embedded_frame_sink_provider.CreateScopedOverrideMojoInterface(
&embedded_frame_sink_provider_binding);
EXPECT_CALL(*resource_provider_, Initialize(_, _));
EXPECT_CALL(mock_embedded_frame_sink_provider, ConnectToEmbedder(_, _))
.Times(0);
EXPECT_CALL(mock_embedded_frame_sink_provider, CreateCompositorFrameSink_(_))
.Times(1);
submitter_->OnContextLost();
OnReceivedContextProvider(false, nullptr);
task_environment_.RunUntilIdle();
}
// This test simulates a race condition in which the |video_frame_provider_| is
// destroyed before OnReceivedContextProvider returns.
TEST_F(VideoFrameSubmitterTest, StopUsingProviderDuringContextLost) {
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_SUBMISSION(SubmissionType::kStateChange);
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
submitter_->StopUsingProvider();
task_environment_.RunUntilIdle();
// OnReceivedContextProvider returns. We don't run the actual function
// because it would overwrite our fake |sink_| with a real one.
SubmitSingleFrame();
task_environment_.RunUntilIdle();
}
// Test the behaviour of the ChildLocalSurfaceIdAllocator instance. It checks
// that the LocalSurfaceId is properly set at creation and updated when the
// video frames change.
TEST_F(VideoFrameSubmitterTest, FrameSizeChangeUpdatesLocalSurfaceId) {
{
viz::LocalSurfaceId local_surface_id =
child_local_surface_id_allocator().GetCurrentLocalSurfaceId();
EXPECT_TRUE(local_surface_id.is_valid());
EXPECT_EQ(11u, local_surface_id.parent_sequence_number());
EXPECT_EQ(viz::kInitialChildSequenceNumber,
local_surface_id.child_sequence_number());
EXPECT_TRUE(frame_size().IsEmpty());
}
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_SUBMISSION(SubmissionType::kManual);
SubmitSingleFrame();
task_environment_.RunUntilIdle();
{
viz::LocalSurfaceId local_surface_id =
child_local_surface_id_allocator().GetCurrentLocalSurfaceId();
EXPECT_TRUE(local_surface_id.is_valid());
EXPECT_EQ(11u, local_surface_id.parent_sequence_number());
EXPECT_EQ(viz::kInitialChildSequenceNumber,
local_surface_id.child_sequence_number());
EXPECT_EQ(gfx::Size(8, 8), frame_size());
AckSubmittedFrame();
}
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
.WillOnce(Return(media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, gfx::Size(2, 2), gfx::Rect(gfx::Size(2, 2)),
gfx::Size(2, 2), base::TimeDelta())));
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
EXPECT_CALL(*resource_provider_, AppendQuads(_, _, _, _));
EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
SubmitSingleFrame();
task_environment_.RunUntilIdle();
{
viz::LocalSurfaceId local_surface_id =
child_local_surface_id_allocator().GetCurrentLocalSurfaceId();
EXPECT_TRUE(local_surface_id.is_valid());
EXPECT_EQ(11u, local_surface_id.parent_sequence_number());
EXPECT_EQ(viz::kInitialChildSequenceNumber + 1,
local_surface_id.child_sequence_number());
EXPECT_EQ(gfx::Size(2, 2), frame_size());
}
}
TEST_F(VideoFrameSubmitterTest, VideoRotationOutputRect) {
MakeSubmitter();
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
EXPECT_TRUE(IsRendering());
gfx::Size coded_size(1280, 720);
gfx::Size natural_size(1280, 1024);
gfx::Size rotated_size(1024, 1280);
{
submitter_->SetRotation(media::VideoRotation::VIDEO_ROTATION_90);
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
.WillOnce(Return(true));
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
.WillOnce(Return(media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, coded_size, gfx::Rect(coded_size),
natural_size, base::TimeDelta())));
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
EXPECT_CALL(*resource_provider_,
AppendQuads(_, _, media::VideoRotation::VIDEO_ROTATION_90, _));
EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
EXPECT_EQ(sink_->last_submitted_compositor_frame().size_in_pixels(),
rotated_size);
AckSubmittedFrame();
}
{
submitter_->SetRotation(media::VideoRotation::VIDEO_ROTATION_180);
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
.WillOnce(Return(true));
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
.WillOnce(Return(media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, coded_size, gfx::Rect(coded_size),
natural_size, base::TimeDelta())));
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
EXPECT_CALL(*resource_provider_,
AppendQuads(_, _, media::VideoRotation::VIDEO_ROTATION_180, _));
EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
// 180 deg rotation has same size.
EXPECT_EQ(sink_->last_submitted_compositor_frame().size_in_pixels(),
natural_size);
AckSubmittedFrame();
}
{
submitter_->SetRotation(media::VideoRotation::VIDEO_ROTATION_270);
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
.WillOnce(Return(true));
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
.WillOnce(Return(media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, coded_size, gfx::Rect(coded_size),
natural_size, base::TimeDelta())));
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
EXPECT_CALL(*resource_provider_,
AppendQuads(_, _, media::VideoRotation::VIDEO_ROTATION_270, _));
EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
EXPECT_EQ(sink_->last_submitted_compositor_frame().size_in_pixels(),
rotated_size);
AckSubmittedFrame();
}
}
TEST_F(VideoFrameSubmitterTest, PageVisibilityControlsSubmission) {
// Hide the page and ensure no begin frames are issued.
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
submitter_->SetIsPageVisible(false);
task_environment_.RunUntilIdle();
EXPECT_FALSE(ShouldSubmit());
// Start rendering, but since page is hidden nothing should start yet.
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
// Mark the page as visible and confirm frame submission.
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
EXPECT_SUBMISSION(SubmissionType::kStateChange);
submitter_->SetIsPageVisible(true);
task_environment_.RunUntilIdle();
// Transition back to the page being hidden and ensure begin frames stop.
EXPECT_TRUE(ShouldSubmit());
EXPECT_CALL(*sink_, SetNeedsBeginFrame(false));
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame()).Times(0);
submitter_->SetIsPageVisible(false);
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, PreferredInterval) {
video_frame_provider_->preferred_interval = base::TimeDelta::FromSeconds(1);
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_SUBMISSION(SubmissionType::kBeginFrame);
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
EXPECT_EQ(sink_->last_submitted_compositor_frame()
.metadata.preferred_frame_interval,
video_frame_provider_->preferred_interval);
}
TEST_F(VideoFrameSubmitterTest, NoDuplicateFramesOnBeginFrame) {
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_TRUE(IsRendering());
auto vf = media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
gfx::Size(8, 8), base::TimeDelta());
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
.WillOnce(Return(true));
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame()).WillOnce(Return(vf));
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
EXPECT_CALL(*resource_provider_, AppendQuads(_, _, _, _));
EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
viz::BeginFrameArgs args = begin_frame_source_->CreateBeginFrameArgs(
BEGINFRAME_FROM_HERE, now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
AckSubmittedFrame();
// Trying to submit the same frame again does nothing... even if
// UpdateCurrentFrame() lies about there being a new frame.
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame(_, _))
.WillOnce(Return(true));
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame()).WillOnce(Return(vf));
EXPECT_CALL(*sink_, DidNotProduceFrame(_));
args = begin_frame_source_->CreateBeginFrameArgs(BEGINFRAME_FROM_HERE,
now_src_.get());
submitter_->OnBeginFrame(args, {});
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, NoDuplicateFramesDidReceiveFrame) {
auto vf = media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
gfx::Size(8, 8), base::TimeDelta());
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame()).WillOnce(Return(vf));
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame());
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _));
EXPECT_CALL(*resource_provider_, AppendQuads(_, _, _, _));
EXPECT_CALL(*resource_provider_, PrepareSendToParent(_, _));
EXPECT_CALL(*resource_provider_, ReleaseFrameResources());
submitter_->DidReceiveFrame();
task_environment_.RunUntilIdle();
AckSubmittedFrame();
// Trying to submit the same frame again does nothing...
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame()).WillOnce(Return(vf));
submitter_->DidReceiveFrame();
task_environment_.RunUntilIdle();
}
TEST_F(VideoFrameSubmitterTest, ZeroSizedFramesAreNotSubmitted) {
auto vf = media::VideoFrame::CreateEOSFrame();
ASSERT_TRUE(vf->natural_size().IsEmpty());
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame()).WillOnce(Return(vf));
EXPECT_CALL(*sink_, DoSubmitCompositorFrame(_, _)).Times(0);
submitter_->DidReceiveFrame();
task_environment_.RunUntilIdle();
}
// Check that given enough frames with wallclock duration and enough
// presentation feedback data, VideoFrameSubmitter will call the video roughness
// reporting callback.
TEST_F(VideoFrameSubmitterTest, ProcessTimingDetails) {
int fps = 24;
int reports = 0;
base::TimeDelta frame_duration = base::TimeDelta::FromSecondsD(1.0 / fps);
int frames_to_run =
fps * (cc::VideoPlaybackRoughnessReporter::kMinWindowsBeforeSubmit + 1);
WTF::HashMap<uint32_t, viz::FrameTimingDetails> timing_details;
MakeSubmitter(base::BindLambdaForTesting(
[&](const cc::VideoPlaybackRoughnessReporter::Measurement& measurement) {
ASSERT_EQ(measurement.frame_size.width(), 8);
ASSERT_EQ(measurement.frame_size.height(), 8);
reports++;
}));
EXPECT_CALL(*sink_, SetNeedsBeginFrame(true));
submitter_->StartRendering();
task_environment_.RunUntilIdle();
EXPECT_TRUE(IsRendering());
auto sink_submit = [&](const viz::LocalSurfaceId&,
viz::CompositorFrame* frame) {
auto token = frame->metadata.frame_token;
viz::FrameTimingDetails details;
details.presentation_feedback.timestamp =
base::TimeTicks() + frame_duration * token;
details.presentation_feedback.flags =
gfx::PresentationFeedback::kHWCompletion;
timing_details.clear();
timing_details.Set(token, details);
};
EXPECT_CALL(*video_frame_provider_, UpdateCurrentFrame)
.WillRepeatedly(Return(true));
EXPECT_CALL(*video_frame_provider_, PutCurrentFrame).Times(AnyNumber());
EXPECT_CALL(*sink_, DoSubmitCompositorFrame)
.WillRepeatedly(Invoke(sink_submit));
EXPECT_CALL(*resource_provider_, AppendQuads).Times(AnyNumber());
EXPECT_CALL(*resource_provider_, PrepareSendToParent).Times(AnyNumber());
EXPECT_CALL(*resource_provider_, ReleaseFrameResources).Times(AnyNumber());
for (int i = 0; i < frames_to_run; i++) {
auto frame = media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_YV12, gfx::Size(8, 8), gfx::Rect(gfx::Size(8, 8)),
gfx::Size(8, 8), i * frame_duration);
frame->metadata().wallclock_frame_duration = frame_duration;
EXPECT_CALL(*video_frame_provider_, GetCurrentFrame())
.WillRepeatedly(Return(frame));
auto args = begin_frame_source_->CreateBeginFrameArgs(BEGINFRAME_FROM_HERE,
now_src_.get());
submitter_->OnBeginFrame(args, timing_details);
task_environment_.RunUntilIdle();
AckSubmittedFrame();
}
submitter_->StopRendering();
EXPECT_EQ(reports, 1);
}
} // namespace blink