| // 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/functional/callback_helpers.h" | |
| #include "base/memory/memory_pressure_listener_registry.h" | |
| #include "base/memory_coordinator/memory_coordinator_features.h" | |
| #include "base/memory_coordinator/test_memory_consumer_registry.h" | |
| #include "base/memory_coordinator/utils.h" | |
| #include "base/run_loop.h" | |
| #include "base/test/scoped_feature_list.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 {} | |
| 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::MemoryLimit memory_limit = base::MemoryLimit::Default(); | |
| switch (level) { | |
| case base::MEMORY_PRESSURE_LEVEL_NONE: | |
| memory_limit = base::kNoMemoryPressureThreshold; | |
| break; | |
| case base::MEMORY_PRESSURE_LEVEL_MODERATE: | |
| memory_limit = base::kModerateMemoryPressureThreshold; | |
| break; | |
| case base::MEMORY_PRESSURE_LEVEL_CRITICAL: | |
| memory_limit = base::kCriticalMemoryPressureThreshold; | |
| break; | |
| } | |
| base::RunLoop run_loop; | |
| test_memory_consumer_registry_.NotifyUpdateMemoryLimitAsync( | |
| memory_limit, base::DoNothing()); | |
| test_memory_consumer_registry_.NotifyReleaseMemoryAsync( | |
| run_loop.QuitClosure()); | |
| run_loop.Run(); | |
| } | |
| base::MemoryPressureListenerRegistry memory_pressure_listener_registry_; | |
| base::TestMemoryConsumerRegistry test_memory_consumer_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) { | |
| base::test::ScopedFeatureList scoped_feature_list; | |
| scoped_feature_list.InitAndEnableFeature(base::kStatefulMemoryPressure); | |
| 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))); | |
| } | |
| TEST_F(PassthroughProgramCacheTest, MemoryPressureStateful) { | |
| base::test::ScopedFeatureList scoped_feature_list; | |
| scoped_feature_list.InitAndEnableFeature(base::kStatefulMemoryPressure); | |
| const int kKeyLength = 10; | |
| // Compute a blob length giving us a 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))); | |
| // Moderate pressure (50%). | |
| base::RunLoop run_loop; | |
| test_memory_consumer_registry_.NotifyUpdateMemoryLimitAsync( | |
| base::kModerateMemoryPressureThreshold, run_loop.QuitClosure()); | |
| run_loop.Run(); | |
| // Verify that NO memory is released yet! | |
| EXPECT_NE("", Get(MakeKey(kKeyLength, 0))); | |
| EXPECT_NE("", Get(MakeKey(kKeyLength, 1))); | |
| EXPECT_NE("", Get(MakeKey(kKeyLength, 2))); | |
| EXPECT_NE("", Get(MakeKey(kKeyLength, 3))); | |
| // Now release memory. | |
| base::RunLoop run_loop2; | |
| test_memory_consumer_registry_.NotifyReleaseMemoryAsync( | |
| run_loop2.QuitClosure()); | |
| run_loop2.Run(); | |
| // Now memory should be released! | |
| // At 50% limit, size should be 1/4 (1 item). | |
| EXPECT_EQ("", Get(MakeKey(kKeyLength, 0))); | |
| EXPECT_EQ("", Get(MakeKey(kKeyLength, 1))); | |
| EXPECT_EQ("", Get(MakeKey(kKeyLength, 2))); | |
| EXPECT_NE("", Get(MakeKey(kKeyLength, 3))); | |
| } | |
| } // namespace gles2 | |
| } // namespace gpu |