blob: 7c13a8d0554583206bbe32dfb9b9d3e55063142f [file] [log] [blame]
// 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