blob: 788ed2b94754a4419a229951f0802398c861f39a [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 "media/gpu/android/codec_image_group.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/sequenced_task_runner.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread.h"
#include "media/base/android/mock_android_overlay.h"
#include "media/gpu/android/codec_surface_bundle.h"
#include "media/gpu/android/mock_codec_image.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
// Subclass of CodecImageGroup which will notify us when it's destroyed.
class CodecImageGroupWithDestructionHook : public CodecImageGroup {
public:
CodecImageGroupWithDestructionHook(
scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<CodecSurfaceBundle> surface_bundle)
: CodecImageGroup(std::move(task_runner), std::move(surface_bundle)) {}
void SetDestructionCallback(base::OnceClosure cb) {
destruction_cb_ = std::move(cb);
}
private:
~CodecImageGroupWithDestructionHook() override {
if (destruction_cb_)
std::move(destruction_cb_).Run();
}
base::OnceClosure destruction_cb_;
};
} // namespace
class CodecImageGroupTest : public testing::Test {
public:
CodecImageGroupTest() = default;
void SetUp() override {
gpu_task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>();
}
void TearDown() override {}
struct Record {
scoped_refptr<CodecSurfaceBundle> surface_bundle;
scoped_refptr<CodecImageGroupWithDestructionHook> image_group;
MockAndroidOverlay* overlay() const {
return static_cast<MockAndroidOverlay*>(surface_bundle->overlay());
}
};
// Create an image group for a surface bundle with an overlay.
Record CreateImageGroup() {
std::unique_ptr<MockAndroidOverlay> overlay =
std::make_unique<MockAndroidOverlay>();
EXPECT_CALL(*overlay.get(), MockAddSurfaceDestroyedCallback());
Record rec;
rec.surface_bundle =
base::MakeRefCounted<CodecSurfaceBundle>(std::move(overlay));
rec.image_group = base::MakeRefCounted<CodecImageGroupWithDestructionHook>(
gpu_task_runner_, rec.surface_bundle);
return rec;
}
// Handy method to check that CodecImage destruction is relayed properly.
MOCK_METHOD1(OnCodecImageDestroyed, void(CodecImage*));
base::test::ScopedTaskEnvironment env_;
// Our thread is the mcvd thread. This is the task runner for the gpu thread.
scoped_refptr<base::TestSimpleTaskRunner> gpu_task_runner_;
};
TEST_F(CodecImageGroupTest, GroupRegistersForOverlayDestruction) {
// When we provide an image group with an overlay, it should register for
// destruction on that overlay.
Record rec = CreateImageGroup();
// Note that we don't run any thread loop to completion -- it should assign
// the callback on our thread, since that's where the overlay is used.
// We verify expectations now, so that it doesn't matter if any task runners
// run during teardown. I'm not sure if the expectations would be checked
// before or after that. If after, then posting would still pass, which we
// don't want.
testing::Mock::VerifyAndClearExpectations(this);
// There should not be just one ref to the surface bundle; the CodecImageGroup
// should have one too.
ASSERT_FALSE(rec.surface_bundle->HasOneRef());
}
TEST_F(CodecImageGroupTest, SurfaceBundleWithoutOverlayDoesntCrash) {
// Make sure that it's okay not to have an overlay. CodecImageGroup should
// handle ST surface bundles without crashing.
scoped_refptr<CodecSurfaceBundle> surface_bundle =
base::MakeRefCounted<CodecSurfaceBundle>();
scoped_refptr<CodecImageGroup> image_group =
base::MakeRefCounted<CodecImageGroup>(gpu_task_runner_, surface_bundle);
// TODO(liberato): we should also make sure that adding an image doesn't call
// ReleaseCodecBuffer when it's added.
}
TEST_F(CodecImageGroupTest, ImagesRetainRefToGroup) {
// Make sure that keeping an image around is sufficient to keep the group.
Record rec = CreateImageGroup();
bool was_destroyed = false;
rec.image_group->SetDestructionCallback(
base::BindOnce([](bool* flag) -> void { *flag = true; }, &was_destroyed));
scoped_refptr<CodecImage> image = new MockCodecImage();
// We're supposed to call this from |gpu_task_runner_|, but all
// CodecImageGroup really cares about is being single sequence.
rec.image_group->AddCodecImage(image.get());
// The image should be sufficient to prevent destruction.
rec.image_group = nullptr;
ASSERT_FALSE(was_destroyed);
// The image should be the last ref to the image group.
image = nullptr;
ASSERT_TRUE(was_destroyed);
}
TEST_F(CodecImageGroupTest, RemovingImageAllowsDestructionOfGroup) {
// Removing the last image from the group allows its destruction.
Record rec = CreateImageGroup();
bool was_destroyed = false;
rec.image_group->SetDestructionCallback(
base::BindOnce([](bool* flag) -> void { *flag = true; }, &was_destroyed));
scoped_refptr<CodecImage> image = new MockCodecImage();
rec.image_group->AddCodecImage(image.get());
// Dropping our ref should not delete the group, since the image holds it.
CodecImageGroup* image_group_raw = rec.image_group.get();
rec.image_group = nullptr;
ASSERT_FALSE(was_destroyed);
// Removing the codec image from the group should allow destruction. Note
// that this also (subtly) tests that the CodecImageGroup clears the
// destruction CB that it set on the CodecImage; that callback holds a strong
// ref, so the group won't be destroyed if it doesn't.
image_group_raw->RemoveCodecImage(image.get());
ASSERT_TRUE(was_destroyed);
}
TEST_F(CodecImageGroupTest, ImageGroupDropsForwardsSurfaceDestruction) {
// CodecImageGroup should notify all images when the surface is destroyed. We
// also verify that the image group drops its ref to the surface bundle, so
// that it doesn't prevent destruction of the overlay that provided it.
Record rec = CreateImageGroup();
scoped_refptr<MockCodecImage> image_1 = new MockCodecImage();
scoped_refptr<MockCodecImage> image_2 = new MockCodecImage();
rec.image_group->AddCodecImage(image_1.get());
rec.image_group->AddCodecImage(image_2.get());
// Destroy the surface. All destruction messages should be posted to the
// gpu thread.
EXPECT_CALL(*image_1.get(), ReleaseCodecBuffer()).Times(0);
EXPECT_CALL(*image_2.get(), ReleaseCodecBuffer()).Times(0);
// Note that we're calling this on the wrong thread, but that's okay.
rec.overlay()->OnSurfaceDestroyed();
env_.RunUntilIdle();
// Run the main loop and guarantee that nothing has run. It should be posted
// to |gpu_task_runner_|.
testing::Mock::VerifyAndClearExpectations(this);
// Now run |gpu_task_runner_| and verify that the callbacks run.
EXPECT_CALL(*image_1.get(), ReleaseCodecBuffer()).Times(1);
EXPECT_CALL(*image_2.get(), ReleaseCodecBuffer()).Times(1);
gpu_task_runner_->RunUntilIdle();
testing::Mock::VerifyAndClearExpectations(this);
// The image group should drop its ref to the surface bundle.
ASSERT_TRUE(rec.surface_bundle->HasOneRef());
}
} // namespace media