blob: be92a03c8f3b785453237c051a4d576cd4fa0358 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/test/test_gpu_service_holder.h"
#include <tuple>
#include <utility>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "components/viz/service/gl/gpu_service_impl.h"
#include "gpu/command_buffer/service/scheduler_sequence.h"
#include "gpu/command_buffer/service/service_utils.h"
#include "gpu/config/gpu_feature_info.h"
#include "gpu/config/gpu_info.h"
#include "gpu/config/gpu_info_collector.h"
#include "gpu/config/gpu_preferences.h"
#include "gpu/config/gpu_util.h"
#include "gpu/ipc/service/gpu_watchdog_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/init/gl_factory.h"
#if BUILDFLAG(ENABLE_VULKAN)
#include "gpu/vulkan/init/vulkan_factory.h"
#include "gpu/vulkan/vulkan_implementation.h"
#endif
#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/gpu_platform_support_host.h"
#include "ui/ozone/public/ozone_platform.h"
#endif
namespace viz {
namespace {
#if BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_FUCHSIA)
namespace {
constexpr int kGpuProcessHostId = 1;
} // namespace
#endif
base::Lock& GetLock() {
static base::NoDestructor<base::Lock> lock;
return *lock;
}
// We expect GetLock() to be acquired before accessing these variables.
TestGpuServiceHolder* g_holder = nullptr;
bool g_disallow_feature_list_overrides = true;
bool g_should_register_listener = true;
bool g_registered_listener = false;
class InstanceResetter
: public testing::EmptyTestEventListener,
public base::test::TaskEnvironment::DestructionObserver {
public:
InstanceResetter() {
base::test::TaskEnvironment::AddDestructionObserver(this);
}
InstanceResetter(const InstanceResetter&) = delete;
InstanceResetter& operator=(const InstanceResetter&) = delete;
~InstanceResetter() override {
base::test::TaskEnvironment::RemoveDestructionObserver(this);
}
// testing::EmptyTestEventListener:
void OnTestEnd(const testing::TestInfo& test_info) override {
{
base::AutoLock locked(GetLock());
// Make sure the TestGpuServiceHolder instance is not re-created after
// WillDestroyCurrentTaskEnvironment().
// Otherwise we'll end up with GPU tasks weirdly running in a different
// context after the test.
DCHECK(!(reset_by_task_env && g_holder))
<< "TestGpuServiceHolder was re-created after "
"base::test::TaskEnvironment was destroyed.";
}
reset_by_task_env = false;
TestGpuServiceHolder::ResetInstance();
}
// base::test::TaskEnvironment::DestructionObserver:
void WillDestroyCurrentTaskEnvironment() override {
reset_by_task_env = true;
TestGpuServiceHolder::ResetInstance();
}
private:
bool reset_by_task_env = false;
};
} // namespace
// static
TestGpuServiceHolder* TestGpuServiceHolder::GetInstance() {
base::AutoLock locked(GetLock());
// Make sure the global TestGpuServiceHolder is delete after each test. The
// listener will always be registered with gtest even if gtest isn't
// otherwised used. This should do nothing in the non-gtest case.
if (!g_registered_listener && g_should_register_listener) {
g_registered_listener = true;
testing::TestEventListeners& listeners =
testing::UnitTest::GetInstance()->listeners();
// |listeners| assumes ownership of InstanceResetter.
listeners.Append(new InstanceResetter);
}
// Make sure the global TestGpuServiceHolder is deleted at process exit.
static bool registered_cleanup = false;
if (!registered_cleanup) {
registered_cleanup = true;
base::AtExitManager::RegisterTask(
base::BindOnce(&TestGpuServiceHolder::ResetInstance));
}
if (!g_holder) {
g_holder = new TestGpuServiceHolder(gpu::gles2::ParseGpuPreferences(
base::CommandLine::ForCurrentProcess()));
}
return g_holder;
}
// static
void TestGpuServiceHolder::ResetInstance() {
base::AutoLock locked(GetLock());
if (g_holder) {
delete g_holder;
g_holder = nullptr;
}
}
// static
void TestGpuServiceHolder::DoNotResetOnTestExit() {
base::AutoLock locked(GetLock());
// This must be called before GetInstance() is ever called.
DCHECK(!g_registered_listener);
g_should_register_listener = false;
}
TestGpuServiceHolder::ScopedAllowRacyFeatureListOverrides::
ScopedAllowRacyFeatureListOverrides() {
base::AutoLock locked(GetLock());
// This must be called before GetInstance() is ever called.
DCHECK(!g_holder);
DCHECK(g_disallow_feature_list_overrides);
g_disallow_feature_list_overrides = false;
}
TestGpuServiceHolder::ScopedAllowRacyFeatureListOverrides::
~ScopedAllowRacyFeatureListOverrides() {
base::AutoLock locked(GetLock());
DCHECK(!g_disallow_feature_list_overrides);
g_disallow_feature_list_overrides = true;
}
TestGpuServiceHolder::TestGpuServiceHolder(
const gpu::GpuPreferences& gpu_preferences)
: gpu_main_thread_("GPUMainThread"), io_thread_("GPUIOThread") {
if (g_disallow_feature_list_overrides) {
disallow_feature_overrides_.emplace(
"FeatureList overrides must happen before the GPU service thread has "
"been started.");
}
base::Thread::Options gpu_thread_options;
#if BUILDFLAG(IS_OZONE)
gpu_thread_options.message_pump_type = ui::OzonePlatform::GetInstance()
->GetPlatformProperties()
.message_pump_type_for_gpu;
#endif
CHECK(gpu_main_thread_.StartWithOptions(std::move(gpu_thread_options)));
CHECK(io_thread_.Start());
base::WaitableEvent completion;
gpu_main_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&TestGpuServiceHolder::InitializeOnGpuThread,
base::Unretained(this), gpu_preferences, &completion));
completion.Wait();
#if BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_FUCHSIA)
if (auto* gpu_platform_support_host =
ui::OzonePlatform::GetInstance()->GetGpuPlatformSupportHost()) {
auto interface_binder = base::BindRepeating(
&TestGpuServiceHolder::BindInterface, base::Unretained(this));
gpu_platform_support_host->OnGpuServiceLaunched(
kGpuProcessHostId, interface_binder, base::DoNothing());
}
#endif
}
TestGpuServiceHolder::~TestGpuServiceHolder() {
#if BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_FUCHSIA)
if (auto* gpu_platform_support_host =
ui::OzonePlatform::GetInstance()->GetGpuPlatformSupportHost()) {
gpu_platform_support_host->OnChannelDestroyed(kGpuProcessHostId);
}
#endif
// Ensure members created on GPU thread are destroyed there too.
gpu_main_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&TestGpuServiceHolder::DeleteOnGpuThread,
base::Unretained(this)));
gpu_main_thread_.Stop();
io_thread_.Stop();
}
scoped_refptr<gpu::SharedContextState>
TestGpuServiceHolder::GetCompositorGpuThreadSharedContextState() {
if (gpu_service_->compositor_gpu_thread()) {
return gpu_service_->compositor_gpu_thread()->GetSharedContextState();
}
return GetSharedContextState();
}
scoped_refptr<gpu::SharedContextState>
TestGpuServiceHolder::GetSharedContextState() {
return gpu_service_->GetContextState();
}
scoped_refptr<gl::GLShareGroup> TestGpuServiceHolder::GetShareGroup() {
return gpu_service_->share_group();
}
void TestGpuServiceHolder::ScheduleGpuMainTask(base::OnceClosure callback) {
DCHECK(gpu_main_task_sequence_);
gpu_main_task_sequence_->ScheduleTask(std::move(callback), {});
}
void TestGpuServiceHolder::ScheduleCompositorGpuTask(
base::OnceClosure callback) {
if (compositor_gpu_task_sequence_)
compositor_gpu_task_sequence_->ScheduleTask(std::move(callback), {});
else
ScheduleGpuMainTask(std::move(callback));
}
void TestGpuServiceHolder::InitializeOnGpuThread(
const gpu::GpuPreferences& gpu_preferences,
base::WaitableEvent* completion) {
DCHECK(gpu_main_thread_.task_runner()->BelongsToCurrentThread());
#if BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_FUCHSIA)
ui::OzonePlatform::GetInstance()->AddInterfaces(&binders_);
#endif
if (gpu_preferences.use_vulkan != gpu::VulkanImplementationName::kNone) {
#if BUILDFLAG(ENABLE_VULKAN)
bool use_swiftshader = gpu_preferences.use_vulkan ==
gpu::VulkanImplementationName::kSwiftshader;
vulkan_implementation_ = gpu::CreateVulkanImplementation(use_swiftshader);
if (!vulkan_implementation_ ||
!vulkan_implementation_->InitializeVulkanInstance(
!gpu_preferences.disable_vulkan_surface)) {
LOG(FATAL) << "Failed to create and initialize Vulkan implementation.";
}
#else
NOTREACHED();
#endif
}
// Always enable gpu and oop raster, regardless of platform and blocklist.
// The latter instructs GpuChannelManager::GetSharedContextState to create a
// GrContext, which is required by SkiaRenderer as well as OOP-R.
gpu::GPUInfo gpu_info;
gpu::GpuFeatureInfo gpu_feature_info = gpu::ComputeGpuFeatureInfo(
gpu_info, gpu_preferences, base::CommandLine::ForCurrentProcess(),
/*needs_more_info=*/nullptr);
gpu_feature_info.status_values[gpu::GPU_FEATURE_TYPE_GPU_RASTERIZATION] =
gpu::kGpuFeatureStatusEnabled;
// On MacOS, the default texture target for native GpuMemoryBuffers is
// GL_TEXTURE_RECTANGLE_ARB. This is due to CGL's requirements for creating
// a GL surface. However, when ANGLE is used on top of SwiftShader or Metal,
// it's necessary to use GL_TEXTURE_2D instead.
// TODO(crbug.com/1056312): The proper behavior is to check the config
// parameter set by the EGL_ANGLE_iosurface_client_buffer extension
#if BUILDFLAG(IS_MAC)
if (gl::GetGLImplementation() == gl::kGLImplementationEGLANGLE &&
(gl::GetANGLEImplementation() == gl::ANGLEImplementation::kSwiftShader ||
gl::GetANGLEImplementation() == gl::ANGLEImplementation::kMetal)) {
gpu::SetMacOSSpecificTextureTarget(GL_TEXTURE_2D);
}
#endif // BUILDFLAG(IS_MAC)
// TODO(rivr): Investigate why creating a GPUInfo and GpuFeatureInfo from
// the command line causes the test SkiaOutputSurfaceImplTest.SubmitPaint to
// fail on Android.
gpu_service_ = std::make_unique<GpuServiceImpl>(
gpu::GPUInfo(), /*watchdog_thread=*/nullptr, io_thread_.task_runner(),
gpu_feature_info, gpu_preferences,
/*gpu_info_for_hardware_gpu=*/gpu::GPUInfo(),
/*gpu_feature_info_for_hardware_gpu=*/gpu::GpuFeatureInfo(),
/*gpu_extra_info=*/gfx::GpuExtraInfo(),
#if BUILDFLAG(ENABLE_VULKAN)
vulkan_implementation_.get(),
#else
/*vulkan_implementation=*/nullptr,
#endif
/*exit_callback=*/base::DoNothing());
// Use a disconnected mojo remote for GpuHost, we don't need to receive any
// messages.
mojo::PendingRemote<mojom::GpuHost> gpu_host_proxy;
std::ignore = gpu_host_proxy.InitWithNewPipeAndPassReceiver();
gpu_service_->InitializeWithHost(
std::move(gpu_host_proxy), gpu::GpuProcessActivityFlags(),
gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size()),
/*sync_point_manager=*/nullptr, /*shared_image_manager=*/nullptr,
/*scheduler=*/nullptr, /*shutdown_event=*/nullptr);
main_task_executor_ = std::make_unique<gpu::GpuInProcessThreadService>(
this, gpu_main_thread_.task_runner(), gpu_service_->GetGpuScheduler(),
gpu_service_->sync_point_manager(), gpu_service_->mailbox_manager(),
gpu_service_->gpu_channel_manager()
->default_offscreen_surface()
->GetFormat(),
gpu_service_->gpu_feature_info(),
gpu_service_->gpu_channel_manager()->gpu_preferences(),
gpu_service_->shared_image_manager(),
gpu_service_->gpu_channel_manager()->program_cache());
// TODO(weiliangc): Since SkiaOutputSurface should not depend on command
// buffer, the |gpu_main_task_sequence_| should be coming from
// SkiaOutputSurfaceDependency. SkiaOutputSurfaceDependency cannot be
// initialized here because the it will not have correct client thread set up
// when unit tests are running in parallel.
gpu_main_task_sequence_ = main_task_executor_->CreateSequence();
if (gpu_service_->compositor_gpu_thread()) {
compositor_gpu_task_sequence_ = std::make_unique<gpu::SchedulerSequence>(
gpu_service_->GetGpuScheduler(),
gpu_service_->compositor_gpu_task_runner());
}
completion->Signal();
}
void TestGpuServiceHolder::DeleteOnGpuThread() {
main_task_executor_.reset();
gpu_main_task_sequence_.reset();
compositor_gpu_task_sequence_.reset();
gpu_service_.reset();
}
#if BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_FUCHSIA)
void TestGpuServiceHolder::BindInterface(
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) {
// The interfaces must be bound on the gpu to ensure the mojo calls happen
// on the correct sequence (same happens when the browser runs with a real
// gpu service).
gpu_main_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&TestGpuServiceHolder::BindInterfaceOnGpuThread,
base::Unretained(this), interface_name,
std::move(interface_pipe)));
}
void TestGpuServiceHolder::BindInterfaceOnGpuThread(
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) {
mojo::GenericPendingReceiver receiver =
mojo::GenericPendingReceiver(interface_name, std::move(interface_pipe));
CHECK(binders_.TryBind(&receiver))
<< "Unable to find mojo interface " << interface_name;
}
#endif // BUILDFLAG(IS_OZONE) && !BUILDFLAG(IS_FUCHSIA)
} // namespace viz