blob: 0c28a05e0ee2905cff873ad7c6935af0de63e99f [file] [log] [blame]
// Copyright 2013 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 "base/command_line.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "build/build_config.h"
#include "components/viz/common/gpu/context_provider.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/compositor/image_transport_factory.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/gpu_data_manager.h"
#include "content/public/browser/gpu_utils.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/gpu_stream_constants.h"
#include "content/public/test/content_browser_test.h"
#include "content/test/gpu_browsertest_helpers.h"
#include "gpu/ipc/client/command_buffer_proxy_impl.h"
#include "gpu/ipc/client/gpu_channel_host.h"
#include "services/viz/privileged/interfaces/gl/gpu_service.mojom.h"
#include "services/ws/public/cpp/gpu/context_provider_command_buffer.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "ui/gl/gl_switches.h"
namespace {
// RunLoop implementation that runs until it observes OnContextLost().
class ContextLostRunLoop : public viz::ContextLostObserver {
public:
ContextLostRunLoop(viz::ContextProvider* context_provider)
: context_provider_(context_provider) {
context_provider_->AddObserver(this);
}
~ContextLostRunLoop() override { context_provider_->RemoveObserver(this); }
void RunUntilContextLost() { run_loop_.Run(); }
private:
// viz::LostContextProvider:
void OnContextLost() override { run_loop_.Quit(); }
viz::ContextProvider* const context_provider_;
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(ContextLostRunLoop);
};
class ContextTestBase : public content::ContentBrowserTest {
public:
void SetUpOnMainThread() override {
// This may leave the provider_ null in some cases, so tests need to early
// out.
if (!content::GpuDataManager::GetInstance()->GpuAccessAllowed(nullptr))
return;
scoped_refptr<gpu::GpuChannelHost> gpu_channel_host =
content::GpuBrowsertestEstablishGpuChannelSyncRunLoop();
CHECK(gpu_channel_host);
provider_ =
content::GpuBrowsertestCreateContext(std::move(gpu_channel_host));
auto result = provider_->BindToCurrentThread();
CHECK_EQ(result, gpu::ContextResult::kSuccess);
gl_ = provider_->ContextGL();
context_support_ = provider_->ContextSupport();
ContentBrowserTest::SetUpOnMainThread();
}
void TearDownOnMainThread() override {
// Must delete the context first.
provider_ = nullptr;
ContentBrowserTest::TearDownOnMainThread();
}
protected:
gpu::gles2::GLES2Interface* gl_ = nullptr;
gpu::ContextSupport* context_support_ = nullptr;
private:
scoped_refptr<ws::ContextProviderCommandBuffer> provider_;
};
} // namespace
// Include the shared tests.
#define CONTEXT_TEST_F IN_PROC_BROWSER_TEST_F
#include "base/bind.h"
#include "content/public/browser/browser_thread.h"
#include "gpu/ipc/client/gpu_context_tests.h"
namespace content {
class BrowserGpuChannelHostFactoryTest : public ContentBrowserTest {
public:
void SetUpOnMainThread() override {
if (!GpuDataManager::GetInstance()->GpuAccessAllowed(nullptr))
return;
CHECK(GetFactory());
ContentBrowserTest::SetUpOnMainThread();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// Start all tests without a gpu channel so that the tests exercise a
// consistent codepath.
command_line->AppendSwitch(switches::kDisableGpuEarlyInit);
}
void Signal(bool* event,
scoped_refptr<gpu::GpuChannelHost> gpu_channel_host) {
CHECK_EQ(*event, false);
*event = true;
gpu_channel_host_ = std::move(gpu_channel_host);
}
void SignalAndQuitLoop(bool* event,
base::RunLoop* run_loop,
scoped_refptr<gpu::GpuChannelHost> gpu_channel_host) {
Signal(event, std::move(gpu_channel_host));
run_loop->Quit();
}
protected:
gpu::GpuChannelEstablishFactory* GetFactory() {
return BrowserMainLoop::GetInstance()->gpu_channel_establish_factory();
}
bool IsChannelEstablished() {
return gpu_channel_host_ && !gpu_channel_host_->IsLost();
}
void EstablishAndWait() {
gpu_channel_host_ = content::GpuBrowsertestEstablishGpuChannelSyncRunLoop();
}
gpu::GpuChannelHost* GetGpuChannel() { return gpu_channel_host_.get(); }
scoped_refptr<gpu::GpuChannelHost> gpu_channel_host_;
};
// Test fails on Chromeos + Mac, flaky on Windows because UI Compositor
// establishes a GPU channel.
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
#define MAYBE_Basic Basic
#else
#define MAYBE_Basic DISABLED_Basic
#endif
IN_PROC_BROWSER_TEST_F(BrowserGpuChannelHostFactoryTest, MAYBE_Basic) {
DCHECK(!IsChannelEstablished());
EstablishAndWait();
EXPECT_TRUE(GetGpuChannel() != nullptr);
}
#if !defined(OS_ANDROID)
// Test fails on Chromeos + Mac, flaky on Windows because UI Compositor
// establishes a GPU channel.
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
#define MAYBE_AlreadyEstablished AlreadyEstablished
#else
#define MAYBE_AlreadyEstablished DISABLED_AlreadyEstablished
#endif
IN_PROC_BROWSER_TEST_F(BrowserGpuChannelHostFactoryTest,
MAYBE_AlreadyEstablished) {
DCHECK(!IsChannelEstablished());
scoped_refptr<gpu::GpuChannelHost> gpu_channel =
GetFactory()->EstablishGpuChannelSync();
// Expect established callback immediately.
bool event = false;
GetFactory()->EstablishGpuChannel(
base::BindOnce(&BrowserGpuChannelHostFactoryTest::Signal,
base::Unretained(this), &event));
EXPECT_TRUE(event);
EXPECT_EQ(gpu_channel.get(), GetGpuChannel());
}
#endif
// Test fails on Windows because GPU Channel set-up fails.
#if !defined(OS_WIN)
#define MAYBE_GrContextKeepsGpuChannelAlive GrContextKeepsGpuChannelAlive
#else
#define MAYBE_GrContextKeepsGpuChannelAlive \
DISABLED_GrContextKeepsGpuChannelAlive
#endif
IN_PROC_BROWSER_TEST_F(BrowserGpuChannelHostFactoryTest,
MAYBE_GrContextKeepsGpuChannelAlive) {
// Test for crbug.com/551143
// This test verifies that holding a reference to the GrContext created by
// a ws::ContextProviderCommandBuffer will keep the gpu channel alive after
// the
// provider has been destroyed. Without this behavior, user code would have
// to be careful to destroy objects in the right order to avoid using freed
// memory as a function pointer in the GrContext's GrGLInterface instance.
DCHECK(!IsChannelEstablished());
EstablishAndWait();
// Step 2: verify that holding onto the provider's GrContext will
// retain the host after provider is destroyed.
scoped_refptr<ws::ContextProviderCommandBuffer> provider =
content::GpuBrowsertestCreateContext(GetGpuChannel());
ASSERT_EQ(provider->BindToCurrentThread(), gpu::ContextResult::kSuccess);
sk_sp<GrContext> gr_context = sk_ref_sp(provider->GrContext());
SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(
gr_context.get(), SkBudgeted::kNo, info);
EXPECT_TRUE(surface);
// Destroy the GL context after we made a surface.
provider = nullptr;
// New surfaces will fail to create now.
sk_sp<SkSurface> surface2 =
SkSurface::MakeRenderTarget(gr_context.get(), SkBudgeted::kNo, info);
EXPECT_FALSE(surface2);
// Drop our reference to the gr_context also.
gr_context = nullptr;
// After the context provider is destroyed, the surface no longer has access
// to the GrContext, even though it's alive. Use the canvas after the provider
// and GrContext have been locally unref'ed. This should work fine as the
// GrContext has been abandoned when the GL context provider was destroyed
// above.
SkPaint greenFillPaint;
greenFillPaint.setColor(SK_ColorGREEN);
greenFillPaint.setStyle(SkPaint::kFill_Style);
// Passes by not crashing
surface->getCanvas()->drawRect(SkRect::MakeWH(100, 100), greenFillPaint);
}
// Test fails on Chromeos + Mac, flaky on Windows because UI Compositor
// establishes a GPU channel.
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
#define MAYBE_CrashAndRecover CrashAndRecover
#else
#define MAYBE_CrashAndRecover DISABLED_CrashAndRecover
#endif
IN_PROC_BROWSER_TEST_F(BrowserGpuChannelHostFactoryTest,
MAYBE_CrashAndRecover) {
DCHECK(!IsChannelEstablished());
EstablishAndWait();
scoped_refptr<gpu::GpuChannelHost> host = GetGpuChannel();
scoped_refptr<ws::ContextProviderCommandBuffer> provider =
content::GpuBrowsertestCreateContext(GetGpuChannel());
ContextLostRunLoop run_loop(provider.get());
ASSERT_EQ(provider->BindToCurrentThread(), gpu::ContextResult::kSuccess);
GpuProcessHost::CallOnIO(GpuProcessHost::GPU_PROCESS_KIND_SANDBOXED,
false /* force_create */,
base::BindOnce([](GpuProcessHost* host) {
if (host)
host->gpu_service()->Crash();
}));
run_loop.RunUntilContextLost();
EXPECT_FALSE(IsChannelEstablished());
EstablishAndWait();
EXPECT_TRUE(IsChannelEstablished());
}
using GpuProcessHostBrowserTest = BrowserGpuChannelHostFactoryTest;
IN_PROC_BROWSER_TEST_F(GpuProcessHostBrowserTest, Shutdown) {
DCHECK(!IsChannelEstablished());
EstablishAndWait();
base::RunLoop run_loop;
StopGpuProcess(run_loop.QuitClosure());
run_loop.Run();
}
// Disabled outside linux like other tests here sadface.
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(BrowserGpuChannelHostFactoryTest, CreateTransferBuffer) {
DCHECK(!IsChannelEstablished());
EstablishAndWait();
// This is for an offscreen context, so the default framebuffer doesn't need
// any alpha, depth, stencil, antialiasing.
gpu::ContextCreationAttribs attributes;
attributes.alpha_size = -1;
attributes.depth_size = 0;
attributes.stencil_size = 0;
attributes.samples = 0;
attributes.sample_buffers = 0;
attributes.bind_generates_resource = false;
auto impl = std::make_unique<gpu::CommandBufferProxyImpl>(
GetGpuChannel(), GetFactory()->GetGpuMemoryBufferManager(),
content::kGpuStreamIdDefault, base::ThreadTaskRunnerHandle::Get());
ASSERT_EQ(
impl->Initialize(gpu::kNullSurfaceHandle, nullptr,
content::kGpuStreamPriorityDefault, attributes, GURL()),
gpu::ContextResult::kSuccess);
// Creating a transfer buffer works normally.
int32_t id = -1;
scoped_refptr<gpu::Buffer> buffer = impl->CreateTransferBuffer(100, &id);
EXPECT_TRUE(buffer);
EXPECT_GE(id, 0);
// If the context is lost, creating a transfer buffer still works. This is
// important for initializing a client side context. If it is lost for some
// transient reason, we don't want that to be confused with a fatal error,
// like failing to make a transfer buffer.
// Lose the connection to the gpu to lose the context.
GetGpuChannel()->DestroyChannel();
// It's not visible until we run the task queue.
EXPECT_EQ(impl->GetLastState().error, gpu::error::kNoError);
// Wait to see the error occur. The DestroyChannel() will destroy the IPC
// channel on the IO thread, which then notifies the main thread about the
// error state.
base::RunLoop wait_for_io_run_loop;
base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO})
->PostTask(FROM_HERE, wait_for_io_run_loop.QuitClosure());
// Waits for the IO thread to run.
wait_for_io_run_loop.Run();
// Waits for the main thread to run.
base::RunLoop().RunUntilIdle();
// The error has become visible on the main thread now.
EXPECT_NE(impl->GetLastState().error, gpu::error::kNoError);
// Creating a transfer buffer still works.
id = -1;
buffer = impl->CreateTransferBuffer(100, &id);
EXPECT_TRUE(buffer);
EXPECT_GE(id, 0);
}
#endif
class GpuProcessHostDisableGLBrowserTest : public GpuProcessHostBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
GpuProcessHostBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(switches::kUseGL,
gl::kGLImplementationDisabledName);
}
};
// Android and CrOS don't support disabling GL.
#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(GpuProcessHostDisableGLBrowserTest, CreateAndDestroy) {
DCHECK(!IsChannelEstablished());
EstablishAndWait();
base::RunLoop run_loop;
StopGpuProcess(run_loop.QuitClosure());
run_loop.Run();
}
#endif
} // namespace content