blob: 04941fcaa7a04be41a0ba1d6343d7c52ef8e3eec [file]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpu/command_buffer/service/passthrough_program_cache.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <string>
#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/memory/memory_pressure_listener_registry.h"
#include "base/run_loop.h"
#include "gpu/command_buffer/common/gles2_cmd_format.h"
#include "gpu/command_buffer/common/shm_count.h"
#include "gpu/command_buffer/service/decoder_client.h"
#include "gpu/command_buffer/service/gl_utils.h"
#include "gpu/command_buffer/service/gpu_service_test.h"
#include "gpu/command_buffer/service/test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_mock.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::SetArgPointee;
namespace gpu {
namespace gles2 {
class PassthroughProgramCacheTest : public GpuServiceTest,
public DecoderClient {
public:
static const size_t kCacheSizeBytes = 1024;
static const bool kDisableGpuDiskCache = false;
PassthroughProgramCacheTest()
: cache_(kCacheSizeBytes, kDisableGpuDiskCache), blob_count_(0) {}
~PassthroughProgramCacheTest() override {}
void OnConsoleMessage(int32_t id, const std::string& message) override {}
void CacheBlob(gpu::GpuDiskCacheType type,
const std::string& key,
const std::string& blob) override {}
void OnFenceSyncRelease(uint64_t release) override {}
void OnDescheduleUntilFinished() override {}
void OnRescheduleAfterFinished() override {}
void ScheduleGrContextCleanup() override {}
void HandleReturnData(base::span<const uint8_t> data) override {}
bool ShouldYield() override { return false; }
int32_t blob_count() { return blob_count_; }
protected:
std::string MakeKey(size_t len, uint8_t start_byte) {
std::string binary_key(len, '.');
for (size_t i = 0; i < len; ++i)
binary_key[i] = static_cast<uint8_t>((start_byte + i) & 0xFF);
return binary_key;
}
std::string MakeBlob(size_t len, uint8_t start_byte) {
return MakeKey(len, start_byte);
}
void Set(const std::string& binary_key, const std::string& binary_blob) {
PassthroughProgramCache::BlobCacheSet(
binary_key.data(), static_cast<EGLsizeiANDROID>(binary_key.size()),
binary_blob.data(), static_cast<EGLsizeiANDROID>(binary_blob.size()));
}
std::string Get(const std::string& binary_key) {
EGLsizeiANDROID key_size = static_cast<EGLsizeiANDROID>(binary_key.size());
EGLsizeiANDROID blob_size = PassthroughProgramCache::BlobCacheGet(
binary_key.data(), key_size, nullptr, 0);
if (blob_size <= 0)
return "";
// Directly use std::string for the blob.
std::string binary_blob;
binary_blob.resize(blob_size);
EGLsizeiANDROID blob_size_after = PassthroughProgramCache::BlobCacheGet(
binary_key.data(), key_size, binary_blob.data(), blob_size);
EXPECT_EQ(blob_size, blob_size_after);
return binary_blob;
}
void SimulateMemoryPressure(base::MemoryPressureLevel level) {
base::RunLoop run_loop;
base::MemoryPressureListener::SimulatePressureNotificationAsync(
level, run_loop.QuitClosure());
run_loop.Run();
}
base::MemoryPressureListenerRegistry memory_pressure_listener_registry_;
PassthroughProgramCache cache_;
int32_t blob_count_;
};
TEST_F(PassthroughProgramCacheTest, LoadProgram) {
const int kKeyLength = 10;
const int kBlobLength = 20;
for (uint8_t key_start = 0; key_start < 5; ++key_start) {
std::string binary_key = MakeKey(kKeyLength, key_start);
std::string binary_blob = MakeBlob(kBlobLength, key_start + 10);
// Encode the strings to pretend like they came from disk.
std::string key_string_64 = base::Base64Encode(binary_key);
std::string value_string_64 = base::Base64Encode(binary_blob);
cache_.LoadProgram(key_string_64, value_string_64);
// Make sure the blob was inserted.
EXPECT_EQ(binary_blob, Get(binary_key));
// Test that similar keys but with different length are not retrieved
// (especially making sure the '\0' character at the beginning of the key is
// not causing confusion).
for (size_t i = 0; i <= kKeyLength * 2; ++i) {
if (i != kKeyLength) {
EXPECT_EQ("", Get(MakeKey(i, key_start)));
}
}
}
}
TEST_F(PassthroughProgramCacheTest, BlobSet) {
const int kKeyLength = 10;
const int kBlobLength = 20;
for (uint8_t key_start = 0; key_start < 5; ++key_start) {
std::string binary_key = MakeKey(kKeyLength, key_start);
std::string binary_blob = MakeBlob(kBlobLength, key_start + 25);
Set(binary_key, binary_blob);
// Make sure the blob was inserted.
EXPECT_EQ(binary_blob, Get(binary_key));
// Test that similar keys but with different length are not retrieved
// (especially making sure the '\0' character at the beginning of the key is
// not causing confusion).
for (size_t i = 0; i <= kKeyLength * 2; ++i) {
if (i != kKeyLength) {
EXPECT_EQ("", Get(MakeKey(i, key_start)));
}
}
}
}
TEST_F(PassthroughProgramCacheTest, Eviction) {
const int kKeyLength = 10;
const int kBlobLength = 20;
std::string binary_key = MakeKey(kKeyLength, 0);
std::string binary_blob = MakeBlob(kBlobLength, 0);
Set(binary_key, binary_blob);
EXPECT_EQ(binary_blob, Get(binary_key));
std::string evicting_binary_key = MakeKey(kKeyLength, 1);
std::string evicting_binary_blob =
MakeBlob(kCacheSizeBytes - kBlobLength + 1, 0);
Set(evicting_binary_key, evicting_binary_blob);
// Make sure the new blob is inserted.
EXPECT_EQ(evicting_binary_blob, Get(evicting_binary_key));
// And the old blob is removed.
EXPECT_EQ("", Get(binary_key));
}
TEST_F(PassthroughProgramCacheTest, Clear) {
const int kKeyLength = 10;
const int kBlobLength = 20;
std::string binary_key = MakeKey(kKeyLength, 1);
std::string binary_blob = MakeBlob(kBlobLength, 1);
Set(binary_key, binary_blob);
EXPECT_EQ(binary_blob, Get(binary_key));
cache_.Clear();
// Make sure the blob is removed.
EXPECT_EQ("", Get(binary_key));
}
TEST_F(PassthroughProgramCacheTest, OverwriteOnNewSave) {
const int kKeyLength = 10;
const int kBlobLength = 20;
const int kBlobLength2 = 15;
const int kBlobLength3 = 25;
std::string binary_key = MakeKey(kKeyLength, 0);
std::string binary_blob = MakeBlob(kBlobLength, 0);
std::string binary_blob2 = MakeBlob(kBlobLength2, 78);
std::string binary_blob3 = MakeBlob(kBlobLength3, 113);
Set(binary_key, binary_blob);
EXPECT_EQ(binary_blob, Get(binary_key));
Set(binary_key, binary_blob2);
// Make sure the new blob replaces the old one.
EXPECT_EQ(binary_blob2, Get(binary_key));
Set(binary_key, binary_blob3);
EXPECT_EQ(binary_blob3, Get(binary_key));
}
TEST_F(PassthroughProgramCacheTest, Trim) {
const int kKeyLength = 10;
const int kBlobLength = 20;
std::string binary_key = MakeKey(kKeyLength, 10);
std::string binary_blob = MakeBlob(kBlobLength, 11);
std::string binary_key2 = MakeKey(kKeyLength, 12);
std::string binary_blob2 = MakeBlob(kBlobLength, 13);
Set(binary_key, binary_blob);
Set(binary_key2, binary_blob2);
EXPECT_EQ(binary_blob, Get(binary_key));
EXPECT_EQ(binary_blob2, Get(binary_key2));
// Trimming to exact size of the two blobs shouldn't evict anything.
cache_.Trim(kBlobLength * 2);
EXPECT_EQ(binary_blob, Get(binary_key));
EXPECT_EQ(binary_blob2, Get(binary_key2));
// Trimming to even a byte under that should evict the first (oldest) blob.
cache_.Trim(kBlobLength * 2 - 1);
EXPECT_EQ("", Get(binary_key));
EXPECT_EQ(binary_blob2, Get(binary_key2));
// Trimming to exact size of the blob that's left shouldn't evict anything.
cache_.Trim(kBlobLength);
EXPECT_EQ("", Get(binary_key));
EXPECT_EQ(binary_blob2, Get(binary_key2));
// Trimming more should evict the second blob too.
cache_.Trim(kBlobLength - 1);
EXPECT_EQ("", Get(binary_key));
EXPECT_EQ("", Get(binary_key2));
// Insert the blobs again and this time go from full directly to 0 limit.
Set(binary_key, binary_blob);
Set(binary_key2, binary_blob2);
EXPECT_EQ(binary_blob, Get(binary_key));
EXPECT_EQ(binary_blob2, Get(binary_key2));
cache_.Trim(0);
EXPECT_EQ("", Get(binary_key));
EXPECT_EQ("", Get(binary_key2));
}
TEST_F(PassthroughProgramCacheTest, MemoryPressure) {
const int kKeyLength = 10;
// Compute a blob length giving us a cache capacity of 4 entries.
const int kCacheCapacity = 4;
const int kBlobLength = kCacheSizeBytes / kCacheCapacity;
// Fill the cache.
for (int i = 0; i < kCacheCapacity; i++) {
std::string binary_key = MakeKey(kKeyLength, i);
std::string binary_blob = MakeBlob(kBlobLength, i);
Set(binary_key, binary_blob);
}
EXPECT_NE("", Get(MakeKey(kKeyLength, 0)));
EXPECT_NE("", Get(MakeKey(kKeyLength, 1)));
EXPECT_NE("", Get(MakeKey(kKeyLength, 2)));
EXPECT_NE("", Get(MakeKey(kKeyLength, 3)));
SimulateMemoryPressure(base::MEMORY_PRESSURE_LEVEL_MODERATE);
// Cache size should be reduced to 1/4 of the max size under moderate
// pressure.
EXPECT_EQ("", Get(MakeKey(kKeyLength, 0)));
EXPECT_EQ("", Get(MakeKey(kKeyLength, 1)));
EXPECT_EQ("", Get(MakeKey(kKeyLength, 2)));
EXPECT_NE("", Get(MakeKey(kKeyLength, 3)));
// Adding an item removes the previous one.
Set(MakeKey(kKeyLength, 4), MakeBlob(kBlobLength, 4));
EXPECT_EQ("", Get(MakeKey(kKeyLength, 3)));
EXPECT_NE("", Get(MakeKey(kKeyLength, 4)));
SimulateMemoryPressure(base::MEMORY_PRESSURE_LEVEL_CRITICAL);
EXPECT_EQ("", Get(MakeKey(kKeyLength, 4)));
// Verify new insertions are rejected.
Set(MakeKey(kKeyLength, 5), MakeBlob(kBlobLength, 5));
EXPECT_EQ("", Get(MakeKey(kKeyLength, 5)));
// Return memory pressure state to normal.
SimulateMemoryPressure(base::MEMORY_PRESSURE_LEVEL_NONE);
for (int i = 6; i < 6 + kCacheCapacity; i++) {
std::string binary_key = MakeKey(kKeyLength, i);
std::string binary_blob = MakeBlob(kBlobLength, i);
Set(binary_key, binary_blob);
}
EXPECT_NE("", Get(MakeKey(kKeyLength, 6)));
EXPECT_NE("", Get(MakeKey(kKeyLength, 7)));
EXPECT_NE("", Get(MakeKey(kKeyLength, 8)));
EXPECT_NE("", Get(MakeKey(kKeyLength, 9)));
}
} // namespace gles2
} // namespace gpu