| // 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 <memory> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/logging.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "gpu/command_buffer/service/texture_manager.h" |
| #include "media/base/android/media_codec_bridge.h" |
| #include "media/base/android/mock_media_codec_bridge.h" |
| #include "media/gpu/android/codec_image.h" |
| #include "media/gpu/android/mock_abstract_texture.h" |
| #include "media/gpu/android/mock_texture_owner.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gl/gl_bindings.h" |
| #include "ui/gl/gl_context_egl.h" |
| #include "ui/gl/gl_implementation.h" |
| #include "ui/gl/gl_share_group.h" |
| #include "ui/gl/gl_surface_egl.h" |
| #include "ui/gl/init/gl_factory.h" |
| |
| using testing::InSequence; |
| using testing::Invoke; |
| using testing::NiceMock; |
| using testing::Return; |
| using testing::_; |
| |
| namespace media { |
| |
| class CodecImageTest : public testing::Test { |
| public: |
| CodecImageTest() = default; |
| |
| void SetUp() override { |
| auto codec = std::make_unique<NiceMock<MockMediaCodecBridge>>(); |
| codec_ = codec.get(); |
| wrapper_ = std::make_unique<CodecWrapper>( |
| CodecSurfacePair(std::move(codec), new CodecSurfaceBundle()), |
| base::DoNothing(), base::SequencedTaskRunnerHandle::Get()); |
| ON_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _)) |
| .WillByDefault(Return(MEDIA_CODEC_OK)); |
| |
| gl::init::InitializeGLOneOffImplementation(gl::kGLImplementationEGLGLES2, |
| false, false, false, false); |
| surface_ = new gl::PbufferGLSurfaceEGL(gfx::Size(320, 240)); |
| surface_->Initialize(); |
| share_group_ = new gl::GLShareGroup(); |
| context_ = new gl::GLContextEGL(share_group_.get()); |
| context_->Initialize(surface_.get(), gl::GLContextAttribs()); |
| ASSERT_TRUE(context_->MakeCurrent(surface_.get())); |
| |
| glGenTextures(1, &texture_id_); |
| // The tests rely on this texture being bound. |
| glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id_); |
| |
| auto texture_owner = base::MakeRefCounted<NiceMock<MockTextureOwner>>( |
| texture_id_, context_.get(), surface_.get(), BindsTextureOnUpdate()); |
| codec_buffer_wait_coordinator_ = |
| base::MakeRefCounted<NiceMock<MockCodecBufferWaitCoordinator>>( |
| std::move(texture_owner)); |
| } |
| |
| void TearDown() override { |
| if (texture_id_ && context_->MakeCurrent(surface_.get())) |
| glDeleteTextures(1, &texture_id_); |
| context_ = nullptr; |
| share_group_ = nullptr; |
| surface_ = nullptr; |
| gl::init::ShutdownGL(false); |
| wrapper_->TakeCodecSurfacePair(); |
| } |
| |
| enum ImageKind { kOverlay, kTextureOwner }; |
| scoped_refptr<CodecImage> NewImage( |
| ImageKind kind, |
| CodecImage::DestructionCB destruction_cb = base::DoNothing()) { |
| std::unique_ptr<CodecOutputBuffer> buffer; |
| wrapper_->DequeueOutputBuffer(nullptr, nullptr, &buffer); |
| scoped_refptr<CodecImage> image = new CodecImage(); |
| image->Initialize( |
| std::move(buffer), |
| kind == kTextureOwner ? codec_buffer_wait_coordinator_ : nullptr, |
| base::BindRepeating(&PromotionHintReceiver::OnPromotionHint, |
| base::Unretained(&promotion_hint_receiver_))); |
| |
| image->SetDestructionCB(std::move(destruction_cb)); |
| return image; |
| } |
| |
| virtual bool BindsTextureOnUpdate() { return true; } |
| |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| NiceMock<MockMediaCodecBridge>* codec_; |
| std::unique_ptr<CodecWrapper> wrapper_; |
| scoped_refptr<NiceMock<MockCodecBufferWaitCoordinator>> |
| codec_buffer_wait_coordinator_; |
| scoped_refptr<gl::GLContext> context_; |
| scoped_refptr<gl::GLShareGroup> share_group_; |
| scoped_refptr<gl::GLSurface> surface_; |
| GLuint texture_id_ = 0; |
| |
| class PromotionHintReceiver { |
| public: |
| MOCK_METHOD1(OnPromotionHint, void(PromotionHintAggregator::Hint)); |
| }; |
| |
| PromotionHintReceiver promotion_hint_receiver_; |
| }; |
| |
| class CodecImageTestExplicitBind : public CodecImageTest { |
| bool BindsTextureOnUpdate() override { return false; } |
| }; |
| |
| TEST_F(CodecImageTest, NowUnusedCBRuns) { |
| base::MockCallback<CodecImage::NowUnusedCB> cb; |
| auto i = NewImage(kOverlay); |
| i->SetNowUnusedCB(cb.Get()); |
| EXPECT_CALL(cb, Run(i.get())); |
| i = nullptr; |
| } |
| |
| TEST_F(CodecImageTest, DestructionCBRuns) { |
| base::MockCallback<CodecImage::DestructionCB> cb; |
| auto i = NewImage(kOverlay, cb.Get()); |
| EXPECT_CALL(cb, Run(i.get())); |
| i = nullptr; |
| } |
| |
| TEST_F(CodecImageTest, ImageStartsUnrendered) { |
| auto i = NewImage(kTextureOwner); |
| ASSERT_FALSE(i->was_rendered_to_front_buffer()); |
| } |
| |
| TEST_F(CodecImageTest, CopyTexImageIsInvalidForOverlayImages) { |
| auto i = NewImage(kOverlay); |
| ASSERT_NE(gl::GLImage::COPY, i->ShouldBindOrCopy()); |
| } |
| |
| TEST_F(CodecImageTest, ScheduleOverlayPlaneIsInvalidForTextureOwnerImages) { |
| auto i = NewImage(kTextureOwner); |
| ASSERT_FALSE(i->ScheduleOverlayPlane(gfx::AcceleratedWidget(), 0, |
| gfx::OverlayTransform(), gfx::Rect(), |
| gfx::RectF(), true, nullptr)); |
| } |
| |
| TEST_F(CodecImageTest, CopyTexImageFailsIfTargetIsNotOES) { |
| auto i = NewImage(kTextureOwner); |
| ASSERT_FALSE(i->CopyTexImage(GL_TEXTURE_2D)); |
| } |
| |
| TEST_F(CodecImageTest, CopyTexImageFailsIfTheWrongTextureIsBound) { |
| auto i = NewImage(kTextureOwner); |
| GLuint wrong_texture_id; |
| glGenTextures(1, &wrong_texture_id); |
| glBindTexture(GL_TEXTURE_EXTERNAL_OES, wrong_texture_id); |
| ASSERT_FALSE(i->CopyTexImage(GL_TEXTURE_EXTERNAL_OES)); |
| } |
| |
| TEST_F(CodecImageTest, CopyTexImageCanBeCalledRepeatedly) { |
| auto i = NewImage(kTextureOwner); |
| ASSERT_TRUE(i->CopyTexImage(GL_TEXTURE_EXTERNAL_OES)); |
| ASSERT_TRUE(i->CopyTexImage(GL_TEXTURE_EXTERNAL_OES)); |
| } |
| |
| TEST_F(CodecImageTest, CopyTexImageTriggersFrontBufferRendering) { |
| auto i = NewImage(kTextureOwner); |
| // Verify that the release comes before the wait. |
| InSequence s; |
| EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, true)); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_, WaitForFrameAvailable()); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| UpdateTexImage()); |
| i->CopyTexImage(GL_TEXTURE_EXTERNAL_OES); |
| ASSERT_TRUE(i->was_rendered_to_front_buffer()); |
| } |
| |
| TEST_F(CodecImageTestExplicitBind, CopyTexImageTriggersFrontBufferRendering) { |
| auto i = NewImage(kTextureOwner); |
| // Verify that the release comes before the wait. |
| InSequence s; |
| EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, true)); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_, WaitForFrameAvailable()); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| UpdateTexImage()); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| EnsureTexImageBound()); |
| i->CopyTexImage(GL_TEXTURE_EXTERNAL_OES); |
| ASSERT_TRUE(i->was_rendered_to_front_buffer()); |
| } |
| |
| TEST_F(CodecImageTest, GetTextureMatrixTriggersFrontBufferRendering) { |
| auto i = NewImage(kTextureOwner); |
| InSequence s; |
| EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, true)); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_, WaitForFrameAvailable()); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| UpdateTexImage()); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| GetTransformMatrix(_)); |
| float matrix[16]; |
| i->GetTextureMatrix(matrix); |
| ASSERT_TRUE(i->was_rendered_to_front_buffer()); |
| } |
| |
| TEST_F(CodecImageTestExplicitBind, |
| GetTextureMatrixTriggersFrontBufferRendering) { |
| // GetTextureMatrix should not bind the image. |
| codec_buffer_wait_coordinator_->texture_owner()->expect_update_tex_image = |
| false; |
| |
| auto i = NewImage(kTextureOwner); |
| InSequence s; |
| EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, true)); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_, WaitForFrameAvailable()); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| UpdateTexImage()); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| GetTransformMatrix(_)); |
| float matrix[16]; |
| i->GetTextureMatrix(matrix); |
| ASSERT_TRUE(i->was_rendered_to_front_buffer()); |
| } |
| |
| TEST_F(CodecImageTest, GetTextureMatrixReturnsIdentityForOverlayImages) { |
| auto i = NewImage(kOverlay); |
| float matrix[16]{0}; |
| i->GetTextureMatrix(matrix); |
| // See GetTextureMatrix() for the expected result. |
| ASSERT_EQ(matrix[0], 1); |
| ASSERT_EQ(matrix[5], -1); |
| } |
| |
| TEST_F(CodecImageTest, ScheduleOverlayPlaneTriggersFrontBufferRendering) { |
| auto i = NewImage(kOverlay); |
| EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, true)); |
| // Also verify that it sends the appropriate promotion hint so that the |
| // overlay is positioned properly. |
| PromotionHintAggregator::Hint hint(gfx::Rect(1, 2, 3, 4), true); |
| EXPECT_CALL(promotion_hint_receiver_, OnPromotionHint(hint)); |
| i->ScheduleOverlayPlane(gfx::AcceleratedWidget(), 0, gfx::OverlayTransform(), |
| hint.screen_rect, gfx::RectF(), true, nullptr); |
| ASSERT_TRUE(i->was_rendered_to_front_buffer()); |
| } |
| |
| TEST_F(CodecImageTest, CanRenderTextureOwnerImageToBackBuffer) { |
| auto i = NewImage(kTextureOwner); |
| ASSERT_TRUE(i->RenderToTextureOwnerBackBuffer()); |
| ASSERT_FALSE(i->was_rendered_to_front_buffer()); |
| } |
| |
| TEST_F(CodecImageTest, CodecBufferInvalidationResultsInRenderingFailure) { |
| auto i = NewImage(kTextureOwner); |
| // Invalidate the backing codec buffer. |
| wrapper_->TakeCodecSurfacePair(); |
| ASSERT_FALSE(i->RenderToTextureOwnerBackBuffer()); |
| } |
| |
| TEST_F(CodecImageTest, RenderToBackBufferDoesntWait) { |
| auto i = NewImage(kTextureOwner); |
| InSequence s; |
| EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, true)); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_, SetReleaseTimeToNow()); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_, WaitForFrameAvailable()) |
| .Times(0); |
| ASSERT_TRUE(i->RenderToTextureOwnerBackBuffer()); |
| } |
| |
| TEST_F(CodecImageTest, PromotingTheBackBufferWaits) { |
| auto i = NewImage(kTextureOwner); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_, SetReleaseTimeToNow()).Times(1); |
| i->RenderToTextureOwnerBackBuffer(); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_, WaitForFrameAvailable()); |
| ASSERT_TRUE(i->RenderToFrontBuffer()); |
| } |
| |
| TEST_F(CodecImageTest, PromotingTheBackBufferAlwaysSucceeds) { |
| auto i = NewImage(kTextureOwner); |
| i->RenderToTextureOwnerBackBuffer(); |
| // Invalidating the codec buffer doesn't matter after it's rendered to the |
| // back buffer. |
| wrapper_->TakeCodecSurfacePair(); |
| ASSERT_TRUE(i->RenderToFrontBuffer()); |
| } |
| |
| TEST_F(CodecImageTest, FrontBufferRenderingFailsIfBackBufferRenderingFailed) { |
| auto i = NewImage(kTextureOwner); |
| wrapper_->TakeCodecSurfacePair(); |
| i->RenderToTextureOwnerBackBuffer(); |
| ASSERT_FALSE(i->RenderToFrontBuffer()); |
| } |
| |
| TEST_F(CodecImageTest, RenderToFrontBufferRestoresTextureBindings) { |
| GLuint pre_bound_texture = 0; |
| glGenTextures(1, &pre_bound_texture); |
| glBindTexture(GL_TEXTURE_EXTERNAL_OES, pre_bound_texture); |
| auto i = NewImage(kTextureOwner); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| UpdateTexImage()); |
| i->RenderToFrontBuffer(); |
| GLint post_bound_texture = 0; |
| glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &post_bound_texture); |
| ASSERT_EQ(pre_bound_texture, static_cast<GLuint>(post_bound_texture)); |
| } |
| |
| TEST_F(CodecImageTestExplicitBind, RenderToFrontBufferDoesNotBindTexture) { |
| codec_buffer_wait_coordinator_->texture_owner()->expect_update_tex_image = |
| false; |
| |
| GLuint pre_bound_texture = 0; |
| glGenTextures(1, &pre_bound_texture); |
| glBindTexture(GL_TEXTURE_EXTERNAL_OES, pre_bound_texture); |
| auto i = NewImage(kTextureOwner); |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| UpdateTexImage()); |
| i->RenderToFrontBuffer(); |
| GLint post_bound_texture = 0; |
| glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &post_bound_texture); |
| ASSERT_EQ(pre_bound_texture, static_cast<GLuint>(post_bound_texture)); |
| } |
| |
| TEST_F(CodecImageTest, RenderToFrontBufferRestoresGLContext) { |
| // Make a new context current. |
| scoped_refptr<gl::GLSurface> surface( |
| new gl::PbufferGLSurfaceEGL(gfx::Size(320, 240))); |
| surface->Initialize(); |
| scoped_refptr<gl::GLShareGroup> share_group(new gl::GLShareGroup()); |
| scoped_refptr<gl::GLContext> context(new gl::GLContextEGL(share_group.get())); |
| context->Initialize(surface.get(), gl::GLContextAttribs()); |
| ASSERT_TRUE(context->MakeCurrent(surface.get())); |
| |
| auto i = NewImage(kTextureOwner); |
| // Our context should not be current when UpdateTexImage() is called. |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| UpdateTexImage()) |
| .WillOnce( |
| Invoke([&]() { ASSERT_FALSE(context->IsCurrent(surface.get())); })); |
| i->RenderToFrontBuffer(); |
| // Our context should have been restored. |
| ASSERT_TRUE(context->IsCurrent(surface.get())); |
| |
| context = nullptr; |
| share_group = nullptr; |
| surface = nullptr; |
| } |
| |
| TEST_F(CodecImageTest, ScheduleOverlayPlaneDoesntSendDuplicateHints) { |
| // SOP should send only one promotion hint unless the position changes. |
| auto i = NewImage(kOverlay); |
| // Also verify that it sends the appropriate promotion hint so that the |
| // overlay is positioned properly. |
| PromotionHintAggregator::Hint hint1(gfx::Rect(1, 2, 3, 4), true); |
| PromotionHintAggregator::Hint hint2(gfx::Rect(5, 6, 7, 8), true); |
| EXPECT_CALL(promotion_hint_receiver_, OnPromotionHint(hint1)).Times(1); |
| EXPECT_CALL(promotion_hint_receiver_, OnPromotionHint(hint2)).Times(1); |
| i->ScheduleOverlayPlane(gfx::AcceleratedWidget(), 0, gfx::OverlayTransform(), |
| hint1.screen_rect, gfx::RectF(), true, nullptr); |
| i->ScheduleOverlayPlane(gfx::AcceleratedWidget(), 0, gfx::OverlayTransform(), |
| hint1.screen_rect, gfx::RectF(), true, nullptr); |
| // Sending a different rectangle should send another hint. |
| i->ScheduleOverlayPlane(gfx::AcceleratedWidget(), 0, gfx::OverlayTransform(), |
| hint2.screen_rect, gfx::RectF(), true, nullptr); |
| } |
| |
| TEST_F(CodecImageTest, GetAHardwareBuffer) { |
| auto i = NewImage(kTextureOwner); |
| EXPECT_EQ(codec_buffer_wait_coordinator_->texture_owner() |
| ->get_a_hardware_buffer_count, |
| 0); |
| EXPECT_FALSE(i->was_rendered_to_front_buffer()); |
| |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| UpdateTexImage()); |
| i->GetAHardwareBuffer(); |
| EXPECT_EQ(codec_buffer_wait_coordinator_->texture_owner() |
| ->get_a_hardware_buffer_count, |
| 1); |
| EXPECT_TRUE(i->was_rendered_to_front_buffer()); |
| } |
| |
| } // namespace media |