blob: 29f00fd93a312a9e45b754999df675ad8ab4e5fc [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
#pragma allow_unsafe_libc_calls
#endif
#include "gpu/command_buffer/service/dawn_caching_interface.h"
#include <string>
#include <string_view>
#include "base/test/scoped_feature_list.h"
#include "gpu/command_buffer/service/mocks.h"
#include "gpu/config/gpu_finch_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gpu::webgpu {
namespace {
using ::testing::StrictMock;
class DawnCachingInterfaceTest : public testing::Test {
protected:
static constexpr std::string_view kKey = "cache key";
static constexpr std::string_view kData = "some data";
static constexpr size_t kKeySize = kKey.size();
static constexpr size_t kDataSize = kData.size();
static constexpr gpu::GpuDiskCacheDawnWebGPUHandle kDawnWebGPUHandle =
gpu::GpuDiskCacheDawnWebGPUHandle(1);
static constexpr gpu::GpuDiskCacheDawnGraphiteHandle kDawnGraphiteHandle =
gpu::GpuDiskCacheDawnGraphiteHandle(2);
DawnCachingInterfaceFactory factory_;
gpu::GpuDiskCacheHandle handle_ = kDawnWebGPUHandle;
StrictMock<MockDecoderClient> decoder_client_mock_;
};
TEST_F(DawnCachingInterfaceTest, LoadNonexistentSize) {
auto dawn_caching_interface = factory_.CreateInstance(handle_);
EXPECT_EQ(
0u, dawn_caching_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
}
TEST_F(DawnCachingInterfaceTest, StoreThenLoadSameInterface) {
auto dawn_caching_interface = factory_.CreateInstance(handle_);
dawn_caching_interface->StoreData(kKey.data(), kKeySize, kData.data(),
kDataSize);
char buffer[kDataSize];
EXPECT_EQ(kDataSize, dawn_caching_interface->LoadData(kKey.data(), kKeySize,
nullptr, 0));
EXPECT_EQ(kDataSize, dawn_caching_interface->LoadData(kKey.data(), kKeySize,
buffer, kDataSize));
EXPECT_EQ(0, memcmp(buffer, kData.data(), kDataSize));
}
TEST_F(DawnCachingInterfaceTest, StoreThenLoadSameHandle) {
auto store_interface = factory_.CreateInstance(handle_);
store_interface->StoreData(kKey.data(), kKeySize, kData.data(), kDataSize);
auto load_interface = factory_.CreateInstance(handle_);
char buffer[kDataSize];
EXPECT_EQ(kDataSize,
load_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
EXPECT_EQ(kDataSize,
load_interface->LoadData(kKey.data(), kKeySize, buffer, kDataSize));
EXPECT_EQ(0, memcmp(buffer, kData.data(), kDataSize));
}
TEST_F(DawnCachingInterfaceTest, StoreDestroyThenLoadSameHandle) {
auto store_interface = factory_.CreateInstance(handle_);
store_interface->StoreData(kKey.data(), kKeySize, kData.data(), kDataSize);
store_interface.reset();
auto load_interface = factory_.CreateInstance(handle_);
char buffer[kDataSize];
EXPECT_EQ(kDataSize,
load_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
EXPECT_EQ(kDataSize,
load_interface->LoadData(kKey.data(), kKeySize, buffer, kDataSize));
EXPECT_EQ(0, memcmp(buffer, kData.data(), kDataSize));
}
// If the handle is released before a new cache is created, the new cache should
// use a new in-memory cache.
TEST_F(DawnCachingInterfaceTest, StoreReleaseThenLoad) {
auto store_interface = factory_.CreateInstance(handle_);
store_interface->StoreData(kKey.data(), kKeySize, kData.data(), kDataSize);
store_interface.reset();
factory_.ReleaseHandle(handle_);
auto load_interface = factory_.CreateInstance(handle_);
EXPECT_EQ(0u, load_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
}
TEST_F(DawnCachingInterfaceTest, IncognitoCachesDoNotShare) {
auto interface_1 = factory_.CreateInstance();
interface_1->StoreData(kKey.data(), kKeySize, kData.data(), kDataSize);
auto interface_2 = factory_.CreateInstance();
EXPECT_EQ(0u, interface_2->LoadData(kKey.data(), kKeySize, nullptr, 0));
}
TEST_F(DawnCachingInterfaceTest, UnableToCreateBackend) {
// This factory mimics what happens when we are unable to create a backend.
DawnCachingInterfaceFactory factory(base::BindRepeating(
[]() -> scoped_refptr<detail::DawnCachingBackend> { return nullptr; }));
// Without an actual backend, all loads and stores should do nothing.
{
auto incongnito_interface = factory.CreateInstance();
incongnito_interface->StoreData(kKey.data(), kKeySize, kData.data(),
kDataSize);
EXPECT_EQ(
0u, incongnito_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
}
{
auto handle_interface = factory.CreateInstance(handle_);
handle_interface->StoreData(kKey.data(), kKeySize, kData.data(), kDataSize);
EXPECT_EQ(0u,
handle_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
}
}
TEST_F(DawnCachingInterfaceTest, StoreTriggersHostSide) {
auto dawn_caching_interface = factory_.CreateInstance(
handle_, base::BindRepeating(&MockDecoderClient::CacheBlob,
base::Unretained(&decoder_client_mock_),
gpu::GpuDiskCacheType::kDawnWebGPU));
EXPECT_CALL(decoder_client_mock_,
CacheBlob(gpu::GpuDiskCacheType::kDawnWebGPU, std::string(kKey),
std::string(kData)));
dawn_caching_interface->StoreData(kKey.data(), kKeySize, kData.data(),
kDataSize);
}
TEST_F(DawnCachingInterfaceTest, TestMaxSizeEviction) {
// Verifies that a cache size that should only fit one entry will only keep
// one entry in memory.
static constexpr std::string_view kKey1 = "1";
static constexpr std::string_view kData1 = "1";
static constexpr std::string_view kKey2 = "2";
static constexpr std::string_view kData2 = "2";
static_assert(kKey1.size() == kKey2.size());
static_assert(kData1.size() == kData2.size());
static constexpr size_t kKeySize = kKey1.size();
static constexpr size_t kDataSize = kData1.size();
static constexpr size_t kCacheSize = 2u * kKeySize + 2u * kDataSize - 1u;
DawnCachingInterfaceFactory factory(base::BindRepeating([]() {
return base::MakeRefCounted<detail::DawnCachingBackend>(kCacheSize);
}));
auto interface = factory.CreateInstance();
interface->StoreData(kKey1.data(), kKeySize, kData1.data(), kDataSize);
interface->StoreData(kKey2.data(), kKeySize, kData2.data(), kDataSize);
EXPECT_EQ(0u, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
EXPECT_EQ(kDataSize, interface->LoadData(kKey2.data(), 1u, nullptr, 0));
}
TEST_F(DawnCachingInterfaceTest, TestLruEviction) {
// Verifies that a cache size that should only fit two entries evicts the
// proper entry when a third one is stored.
static constexpr std::string_view kKey1 = "1";
static constexpr std::string_view kData1 = "1";
static constexpr std::string_view kKey2 = "2";
static constexpr std::string_view kData2 = "2";
static constexpr std::string_view kKey3 = "3";
static constexpr std::string_view kData3 = "3";
static_assert(kKey1.size() == kKey2.size());
static_assert(kKey2.size() == kKey3.size());
static_assert(kData1.size() == kData2.size());
static_assert(kData2.size() == kData3.size());
static constexpr size_t kKeySize = kKey1.size();
static constexpr size_t kDataSize = kData1.size();
static constexpr size_t kCacheSize = 3u * kKeySize + 3u * kDataSize - 1u;
DawnCachingInterfaceFactory factory(base::BindRepeating([]() {
return base::MakeRefCounted<detail::DawnCachingBackend>(kCacheSize);
}));
// Even though Key1 was stored first, because we loaded it once, Key2 should
// be the one to be evicted when Key3 is added.
auto interface = factory.CreateInstance();
interface->StoreData(kKey1.data(), kKeySize, kData1.data(), kDataSize);
interface->StoreData(kKey2.data(), kKeySize, kData2.data(), kDataSize);
EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
interface->StoreData(kKey3.data(), kKeySize, kData3.data(), kDataSize);
EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
EXPECT_EQ(0u, interface->LoadData(kKey2.data(), 1u, nullptr, 0));
EXPECT_EQ(kDataSize, interface->LoadData(kKey3.data(), 1u, nullptr, 0));
}
// Entries that are too large for the size of the cache are not cached and do
// not cause any crashes. This is a regression test for dawn:2034.
TEST_F(DawnCachingInterfaceTest, TestVeryLargeEntrySize) {
static constexpr std::string_view kSmall = "1";
static constexpr std::string_view kLarge = "11111";
static constexpr size_t kSmallSize = kSmall.size();
static constexpr size_t kLargeSize = kLarge.size();
static constexpr size_t kCacheSize = kLargeSize - 1u;
DawnCachingInterfaceFactory factory(base::BindRepeating([]() {
return base::MakeRefCounted<detail::DawnCachingBackend>(kCacheSize);
}));
auto interface = factory.CreateInstance();
{
// When the key is larger than the cache size but the value is not, caching
// fails.
interface->StoreData(kLarge.data(), kLargeSize, kSmall.data(), kSmallSize);
EXPECT_EQ(0u, interface->LoadData(kLarge.data(), kLargeSize, nullptr, 0));
}
{
// When the key is smaller than the cache size, but the value is not,
// caching fails.
interface->StoreData(kSmall.data(), kSmallSize, kLarge.data(), kLargeSize);
EXPECT_EQ(0u, interface->LoadData(kSmall.data(), kSmallSize, nullptr, 0));
}
{
// When the both the key and the value is larger than the cache size,
// caching fails.
interface->StoreData(kLarge.data(), kLargeSize, kLarge.data(), kLargeSize);
EXPECT_EQ(0u, interface->LoadData(kLarge.data(), kLargeSize, nullptr, 0));
}
}
TEST_F(DawnCachingInterfaceTest, TestMemoryPressureCritical) {
// Verifies that on PurgeMemory the cache becomes empty for critical pressure
// levels without `kAggressiveShaderCacheLimits` feature flag.
static constexpr std::string_view kKey1 = "1";
static constexpr std::string_view kData1 = "1";
static constexpr size_t kKeySize = kKey1.size();
static constexpr size_t kDataSize = kData1.size();
static constexpr size_t kCacheSize = 2u * kKeySize + 2u * kDataSize - 1u;
DawnCachingInterfaceFactory factory(base::BindRepeating([]() {
return base::MakeRefCounted<detail::DawnCachingBackend>(kCacheSize);
}));
// Pass handles here so that the backends_ are populated.
auto interfaces = {factory.CreateInstance(kDawnGraphiteHandle),
factory.CreateInstance(kDawnWebGPUHandle)};
for (auto& interface : interfaces) {
interface->StoreData(kKey1.data(), kKeySize, kData1.data(), kDataSize);
EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
factory.PurgeMemory(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
EXPECT_EQ(0u, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
}
}
TEST_F(DawnCachingInterfaceTest, TestAggressiveCacheAndMemoryPressure) {
// Verifies PurgeMemory with `kAggressiveShaderCacheLimits` feature flag.
base::test::ScopedFeatureList feature_list{
::features::kAggressiveShaderCacheLimits};
static constexpr std::string_view kKey1 = "1";
static constexpr std::string_view kData1 = "1";
static constexpr size_t kKeySize = kKey1.size();
static constexpr size_t kDataSize = kData1.size();
static constexpr size_t kCacheSize = 2u * kKeySize + 2u * kDataSize - 1u;
DawnCachingInterfaceFactory factory(base::BindRepeating([]() {
return base::MakeRefCounted<detail::DawnCachingBackend>(kCacheSize);
}));
// Pass handles here so that the backends_ are populated.
auto interfaces = {factory.CreateInstance(kDawnGraphiteHandle),
factory.CreateInstance(kDawnWebGPUHandle)};
for (auto& interface : interfaces) {
interface->StoreData(kKey1.data(), kKeySize, kData1.data(), kDataSize);
EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
// Moderate memory pressure is ignored
factory.PurgeMemory(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
// But not critical, except on Android
factory.PurgeMemory(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
#if BUILDFLAG(IS_ANDROID)
EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
#else
EXPECT_EQ(0u, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
#endif
}
}
} // namespace
} // namespace gpu::webgpu