blob: 2fca3ceff39691112f14f248198bdbf62870765c [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_path_watcher.h"
#include "base/files/file_util.h"
#include "base/run_loop.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "components/viz/common/features.h"
#include "components/viz/host/persistent_cache_sandboxed_file_factory.h"
#include "components/viz/test/gpu_host_impl_test_api.h"
#include "components/viz/test/stub_gpu_service.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h"
#include "gpu/config/gpu_feature_info.h"
#include "gpu/config/gpu_finch_features.h"
#include "media/media_buildflags.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/viz/privileged/mojom/gl/gpu_service.mojom.h"
#include "skia/buildflags.h"
namespace content {
namespace {
// Test implementation of viz::mojom::GpuService which only implements the
// persistent cache aspects.
class TestGpuService : public viz::StubGpuService {
public:
explicit TestGpuService(base::RepeatingClosure quit_closure)
: quit_closure_(quit_closure) {}
TestGpuService(const TestGpuService*) = delete;
~TestGpuService() override = default;
TestGpuService& operator=(const TestGpuService&) = delete;
// mojom::GpuService:
void SetChannelPersistentCacheParams(
int32_t client_id,
const gpu::GpuDiskCacheHandle& handle,
persistent_cache::BackendParams backend_params) override {
quit_closure_.Run();
}
private:
base::RepeatingClosure quit_closure_;
};
class TestCacheFileFactory : public viz::PersistentCacheSandboxedFileFactory {
public:
TestCacheFileFactory(
const base::FilePath& cache_root_dir,
scoped_refptr<base::SequencedTaskRunner> background_task_runner)
: viz::PersistentCacheSandboxedFileFactory(
cache_root_dir,
std::move(background_task_runner)) {}
private:
~TestCacheFileFactory() override = default;
};
} // namespace
// Test GpuHostImpl's behaviors when PersistentCache is used for shader caching.
class GpuHostImplPersistentCacheTest : public ContentBrowserTest {
public:
GpuHostImplPersistentCacheTest()
: gpu_service_thread_("Gpu Service"),
cache_factory_thread_("Cache Factory") {
feature_list_.InitWithFeaturesAndParameters(
{{features::kSkiaGraphite,
{{features::kSkiaGraphiteDawnUsePersistentCache.name, "true"}}}},
{});
CHECK(temp_dir_.CreateUniqueTempDir());
cache_factory_thread_.Start();
cache_factory_ = base::MakeRefCounted<TestCacheFileFactory>(
temp_dir_.GetPath(), cache_factory_thread_.task_runner());
viz::PersistentCacheSandboxedFileFactory::SetInstanceForTesting(
cache_factory_.get());
}
~GpuHostImplPersistentCacheTest() override {
viz::PersistentCacheSandboxedFileFactory::SetInstanceForTesting(nullptr);
cache_factory_thread_.Stop();
}
void PreRunTestOnMainThread() override {
gpu_service_thread_.StartAndWaitForTesting();
run_loop_for_cache_file_wait_ = std::make_unique<base::RunLoop>();
gpu_host_impl_test_api_ = std::make_unique<viz::GpuHostImplTestApi>(
GpuProcessHost::Get()->gpu_host());
mojo::Remote<viz::mojom::GpuService> gpu_service_remote;
auto receiver = gpu_service_remote.BindNewPipeAndPassReceiver();
gpu_host_impl_test_api_->SetGpuService(std::move(gpu_service_remote));
PostTaskToGpuServiceThreadAndWait(
base::BindOnce(&GpuHostImplPersistentCacheTest::InitOnGpuServiceThread,
base::Unretained(this), std::move(receiver),
run_loop_for_cache_file_wait_->QuitClosure()));
ContentBrowserTest::PreRunTestOnMainThread();
}
void PostRunTestOnMainThread() override {
PostTaskToGpuServiceThreadAndWait(
base::BindOnce([](GpuServiceThreadState gpu_service_thread_state) {},
std::move(gpu_service_thread_state_)));
gpu_service_thread_.Stop();
ContentBrowserTest::PostRunTestOnMainThread();
}
void WaitForSetChannelPersistentCacheFile() {
run_loop_for_cache_file_wait_->Run();
}
protected:
void InitOnGpuServiceThread(
mojo::PendingReceiver<viz::mojom::GpuService> receiver,
base::RepeatingClosure quit_closure) {
gpu_service_thread_state_.test_gpu_service =
std::make_unique<TestGpuService>(std::move(quit_closure));
gpu_service_thread_state_.gpu_service_receiver =
std::make_unique<mojo::Receiver<viz::mojom::GpuService>>(
gpu_service_thread_state_.test_gpu_service.get(),
std::move(receiver));
}
void PostTaskToGpuServiceThreadAndWait(base::OnceClosure task) {
base::WaitableEvent completion_event;
gpu_service_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](base::OnceClosure task, base::WaitableEvent* wait_event) {
std::move(task).Run();
wait_event->Signal();
},
std::move(task), &completion_event));
completion_event.Wait();
}
struct GpuServiceThreadState {
std::unique_ptr<TestGpuService> test_gpu_service;
std::unique_ptr<mojo::Receiver<viz::mojom::GpuService>>
gpu_service_receiver;
};
base::test::ScopedFeatureList feature_list_;
base::Thread gpu_service_thread_;
base::Thread cache_factory_thread_;
// RunLoop for waiting on the main thread for SetChannelPersistentCacheFile
// to trigger.
std::unique_ptr<base::RunLoop> run_loop_for_cache_file_wait_;
std::unique_ptr<viz::GpuHostImplTestApi> gpu_host_impl_test_api_;
GpuServiceThreadState gpu_service_thread_state_;
base::ScopedTempDir temp_dir_;
scoped_refptr<TestCacheFileFactory> cache_factory_;
};
#if BUILDFLAG(SKIA_USE_DAWN)
// Check that the cache files exist after SetChannelPersistentCacheFile is
// called.
IN_PROC_BROWSER_TEST_F(GpuHostImplPersistentCacheTest,
SetChannelPersistentCacheFileCalled) {
base::ScopedAllowBlockingForTesting allow_blocking;
WaitForSetChannelPersistentCacheFile();
EXPECT_TRUE(base::PathExists(temp_dir_.GetPath()));
EXPECT_FALSE(base::IsDirectoryEmpty(temp_dir_.GetPath()));
}
// Check that the cache files are cleared after a crash.
IN_PROC_BROWSER_TEST_F(GpuHostImplPersistentCacheTest, ClearCacheOnCrash) {
base::ScopedAllowBlockingForTesting allow_blocking;
WaitForSetChannelPersistentCacheFile();
// The cache directory should exist and not be empty.
EXPECT_TRUE(base::PathExists(temp_dir_.GetPath()));
EXPECT_FALSE(base::IsDirectoryEmpty(temp_dir_.GetPath()));
base::RunLoop run_loop;
// Watch for cache directory changes
base::FilePathWatcher watcher;
watcher.Watch(temp_dir_.GetPath(), base::FilePathWatcher::Type::kNonRecursive,
base::BindRepeating(
[](base::RunLoop* run_loop, const base::FilePath& cache_dir,
const base::FilePath& path, bool error) {
if (base::IsDirectoryEmpty(cache_dir)) {
run_loop->Quit();
}
},
&run_loop, temp_dir_.GetPath()));
// Simulate a crash.
viz::GpuHostImpl* gpu_host_impl = GpuProcessHost::Get()->gpu_host();
gpu::GpuProcessShmCount service_count(
gpu_host_impl->GetShaderCacheShmCountForTesting()->CloneRegion());
gpu::GpuProcessShmCount::ScopedIncrement scoped_increment(&service_count);
gpu_host_impl->OnProcessCrashed();
// The cache directory should be empty after the crash.
run_loop.Run();
EXPECT_TRUE(base::IsDirectoryEmpty(temp_dir_.GetPath()));
}
#endif // BUILDFLAG(SKIA_USE_DAWN)
} // namespace content