blob: 2b7db53a1dc6dee6bdea7a034804c3de38909b15 [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 <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