| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/task_environment.h" |
| #include "gpu/command_buffer/service/mock_texture_owner.h" |
| #include "gpu/command_buffer/service/ref_counted_lock_for_test.h" |
| #include "gpu/command_buffer/service/texture_manager.h" |
| #include "gpu/config/gpu_finch_features.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_codec_buffer_wait_coordinator.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::SequencedTaskRunner::GetCurrentDefault(), |
| gfx::Size(640, 480)); |
| ON_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _)) |
| .WillByDefault(Return(MEDIA_CODEC_OK)); |
| |
| gl::init::InitializeStaticGLBindingsImplementation( |
| gl::GLImplementationParts(gl::kGLImplementationEGLGLES2), false); |
| display_ = gl::init::InitializeGLOneOffPlatformImplementation( |
| /*fallback_to_software_gl=*/false, |
| /*disable_gl_drawing=*/false, |
| /*init_extensions=*/false, |
| /*gpu_preference=*/gl::GpuPreference::kDefault); |
| |
| surface_ = new gl::PbufferGLSurfaceEGL(gl::GLSurfaceEGL::GetGLDisplayEGL(), |
| 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<gpu::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(display_, false); |
| wrapper_->TakeCodecSurfacePair(); |
| } |
| |
| enum ImageKind { kOverlay, kTextureOwner }; |
| scoped_refptr<CodecImage> NewImage( |
| ImageKind kind, |
| CodecImage::UnusedCB unused_cb = base::DoNothing()) { |
| std::unique_ptr<CodecOutputBuffer> buffer; |
| wrapper_->DequeueOutputBuffer(nullptr, nullptr, &buffer); |
| |
| auto codec_buffer_wait_coordinator = |
| kind == kTextureOwner ? codec_buffer_wait_coordinator_ : nullptr; |
| auto buffer_renderer = std::make_unique<CodecOutputBufferRenderer>( |
| std::move(buffer), codec_buffer_wait_coordinator, |
| features::NeedThreadSafeAndroidMedia() |
| ? base::MakeRefCounted<gpu::RefCountedLockForTest>() |
| : nullptr); |
| |
| scoped_refptr<CodecImage> image = |
| new CodecImage(buffer_renderer->size(), |
| features::NeedThreadSafeAndroidMedia() |
| ? base::MakeRefCounted<gpu::RefCountedLockForTest>() |
| : nullptr); |
| image->Initialize( |
| std::move(buffer_renderer), kind == kTextureOwner, |
| base::BindRepeating(&PromotionHintReceiver::OnPromotionHint, |
| base::Unretained(&promotion_hint_receiver_))); |
| |
| image->AddUnusedCB(std::move(unused_cb)); |
| return image; |
| } |
| |
| virtual bool BindsTextureOnUpdate() { return true; } |
| |
| base::test::TaskEnvironment task_environment_; |
| raw_ptr<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; |
| raw_ptr<gl::GLDisplay> display_ = nullptr; |
| |
| 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, UnusedCBRunsOnDestruction) { |
| // Add multiple UnusedCBs and verify that they are all run when the CodecImage |
| // is destroyed. |
| base::MockCallback<CodecImage::UnusedCB> cb_1; |
| base::MockCallback<CodecImage::UnusedCB> cb_2; |
| auto i = NewImage(kOverlay); |
| i->AddUnusedCB(cb_1.Get()); |
| i->AddUnusedCB(cb_2.Get()); |
| EXPECT_CALL(cb_1, Run(i.get())); |
| EXPECT_CALL(cb_2, Run(i.get())); |
| i = nullptr; |
| } |
| |
| TEST_F(CodecImageTest, UnusedCBRunsOnNotifyUnused) { |
| base::MockCallback<CodecImage::UnusedCB> cb_1; |
| base::MockCallback<CodecImage::UnusedCB> cb_2; |
| auto i = NewImage(kTextureOwner); |
| ASSERT_TRUE(i->get_codec_output_buffer_for_testing()); |
| ASSERT_TRUE(i->HasTextureOwner()); |
| i->AddUnusedCB(cb_1.Get()); |
| i->AddUnusedCB(cb_2.Get()); |
| EXPECT_CALL(cb_1, Run(i.get())); |
| EXPECT_CALL(cb_2, Run(i.get())); |
| |
| // Also verify that the output buffer and texture owner are released. |
| i->NotifyUnused(); |
| EXPECT_FALSE(i->get_codec_output_buffer_for_testing()); |
| EXPECT_FALSE(i->HasTextureOwner()); |
| |
| // Verify that an additional call doesn't crash. It should do nothing. |
| i->NotifyUnused(); |
| } |
| |
| TEST_F(CodecImageTest, ImageStartsUnrendered) { |
| auto i = NewImage(kTextureOwner); |
| ASSERT_FALSE(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) { |
| 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( |
| gl::GLSurfaceEGL::GetGLDisplayEGL(), 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); |
| // UpdateTexImage sets it's own context. |
| EXPECT_CALL(*codec_buffer_wait_coordinator_->texture_owner(), |
| UpdateTexImage()); |
| i->RenderToFrontBuffer(); |
| // Our context should have been restored. |
| ASSERT_TRUE(context->IsCurrent(surface.get())); |
| |
| context = nullptr; |
| share_group = nullptr; |
| surface = 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()); |
| } |
| |
| TEST_F(CodecImageTest, GetAHardwareBufferAfterRelease) { |
| // Make sure that we get a nullptr AHB once we've marked the image as unused. |
| auto i = NewImage(kTextureOwner); |
| i->NotifyUnused(); |
| EXPECT_FALSE(i->GetAHardwareBuffer()); |
| } |
| |
| TEST_F(CodecImageTest, RenderAfterUnusedDoesntCrash) { |
| auto i = NewImage(kTextureOwner); |
| i->NotifyUnused(); |
| EXPECT_FALSE(i->RenderToTextureOwnerBackBuffer()); |
| EXPECT_FALSE(i->RenderToTextureOwnerFrontBuffer( |
| CodecImage::BindingsMode::kBindImage, |
| codec_buffer_wait_coordinator_->texture_owner()->GetTextureId())); |
| } |
| |
| } // namespace media |