| /* | |
| * Copyright (C) 2025 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include <SkData.h> | |
| #include <SkRefCnt.h> | |
| #include <SkString.h> | |
| #include <ganesh/GrDirectContext.h> | |
| #include <gtest/gtest.h> | |
| #include <sys/types.h> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <string> | |
| #include "Properties.h" | |
| #include "pipeline/skia/PersistentGraphicsCache.h" | |
| #include "tests/common/TestUtils.h" | |
| // RENDERTHREAD_TEST declares both SkiaVK and SkiaGL variants. | |
| #define VK_ONLY() \ | |
| if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) { \ | |
| GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan"; \ | |
| } | |
| #define ENSURE_FLAG_ENABLED() \ | |
| if (!hwui_flags::separate_pipeline_cache()) { \ | |
| GTEST_SKIP() << "This test is only applicable when the separate_pipeline_cache aconfig " \ | |
| "flag is enabled"; \ | |
| } | |
| using namespace android::uirenderer; | |
| using namespace android::uirenderer::skiapipeline; | |
| namespace { | |
| constexpr char kFilename[] = "pipeline_cache.bin"; | |
| template <typename T> | |
| sk_sp<SkData> createData(const T value) { | |
| return SkData::MakeWithCopy(&value, sizeof(value)); | |
| } | |
| // Hardcoded Skia enum value - tests may break if Skia changes the key. | |
| sk_sp<SkData> getPipelineCacheKey() { | |
| static sk_sp<SkData> keyData = createData<uint32_t>(1); | |
| return keyData; | |
| } | |
| } // namespace | |
| namespace android { | |
| namespace uirenderer { | |
| namespace skiapipeline { | |
| class PersistentGraphicsCacheTestUtils { | |
| private: | |
| class MockGrDirectContextWrapper : public PersistentGraphicsCache::GrDirectContextWrapper { | |
| private: | |
| bool mCanDetectNewVkPipelineCacheData; | |
| bool mHasNewVkPipelineCacheData; | |
| GrDirectContext* mRealContext; | |
| public: | |
| MockGrDirectContextWrapper(bool canDetectNewVkPipelineCacheData, | |
| bool hasNewVkPipelineCacheData, GrDirectContext* realContext) | |
| : mCanDetectNewVkPipelineCacheData(canDetectNewVkPipelineCacheData) | |
| , mHasNewVkPipelineCacheData(hasNewVkPipelineCacheData) | |
| , mRealContext(realContext) {} | |
| bool canDetectNewVkPipelineCacheData() const override { | |
| return mCanDetectNewVkPipelineCacheData; | |
| } | |
| bool hasNewVkPipelineCacheData() const override { return mHasNewVkPipelineCacheData; } | |
| void storeVkPipelineCacheData(size_t maxSize) override { | |
| mRealContext->storeVkPipelineCacheData(maxSize); | |
| } | |
| GrDirectContext* unwrap() const override { return mRealContext; } | |
| }; | |
| static void reset(PersistentGraphicsCache& cache) { | |
| cache.~PersistentGraphicsCache(); | |
| new (&cache) PersistentGraphicsCache(); | |
| } | |
| public: | |
| static PersistentGraphicsCache& newCache(const std::string& path, | |
| useconds_t writeThrottleInterval = 0) { | |
| auto& cache = PersistentGraphicsCache::get(); | |
| reset(cache); | |
| cache.initPipelineCache(path, writeThrottleInterval); | |
| return cache; | |
| } | |
| static void onVkFrameFlushed(PersistentGraphicsCache& cache, | |
| bool canDetectNewVkPipelineCacheData, | |
| bool hasNewVkPipelineCacheData, GrDirectContext* realContext) { | |
| MockGrDirectContextWrapper wrapper(canDetectNewVkPipelineCacheData, | |
| hasNewVkPipelineCacheData, realContext); | |
| cache.onVkFrameFlushed(&wrapper); | |
| } | |
| }; | |
| } // namespace skiapipeline | |
| } // namespace uirenderer | |
| } // namespace android | |
| TEST(PersistentGraphicsCacheTest, emptyFile_loadKey_isEmptyByDefault) { | |
| // Arrange | |
| ENSURE_FLAG_ENABLED(); | |
| auto file = TestFile::ensureExistsEmpty(kFilename); | |
| ASSERT_TRUE(file.has_value()); | |
| auto& cache = PersistentGraphicsCacheTestUtils::newCache(file->path()); | |
| // Act | |
| auto result = cache.load(*getPipelineCacheKey()); | |
| // Assert | |
| ASSERT_EQ(nullptr, result); | |
| } | |
| TEST(PersistentGraphicsCacheTest, store_load_returnsIdenticalData) { | |
| // Arrange | |
| ENSURE_FLAG_ENABLED(); | |
| auto file = TestFile::ensureExistsEmpty(kFilename); | |
| ASSERT_TRUE(file.has_value()); | |
| auto& cache = PersistentGraphicsCacheTestUtils::newCache(file->path()); | |
| auto monitorCreateResult = FileEventMonitor::create(file->path()); | |
| ASSERT_SUCCESS(monitorCreateResult); | |
| uint64_t dataValue = 5; | |
| auto key = createData<uint32_t>(10); | |
| auto data = createData(dataValue); | |
| // Act | |
| cache.store(*key, *data, SkString("VkPipelineCache")); | |
| ASSERT_EQ(FileEventMonitor::AwaitResult::Success, | |
| monitorCreateResult.monitor->awaitWriteOrTimeout()); | |
| auto result = PersistentGraphicsCacheTestUtils::newCache(file->path()).load(*key); | |
| // Assert | |
| ASSERT_NE(nullptr, result); | |
| ASSERT_EQ(sizeof(dataValue), result->size()); | |
| ASSERT_EQ(0, memcmp(&dataValue, result->data(), sizeof(dataValue))); | |
| } | |
| RENDERTHREAD_TEST(PersistentGraphicsCacheTest, hasPipelineCreationCacheControl_newCache_isStored) { | |
| // Arrange | |
| ENSURE_FLAG_ENABLED(); | |
| VK_ONLY(); | |
| auto context = renderThread.getGrContext(); | |
| auto file = TestFile::ensureExistsEmpty(kFilename); | |
| ASSERT_TRUE(file.has_value()); | |
| auto& cache = PersistentGraphicsCacheTestUtils::newCache(file->path()); | |
| auto monitorCreateResult = FileEventMonitor::create(file->path()); | |
| ASSERT_SUCCESS(monitorCreateResult); | |
| // Act | |
| PersistentGraphicsCacheTestUtils::onVkFrameFlushed(cache, | |
| /* canDetectNewVkPipelineCacheData= */ true, | |
| /* hasNewPipelineCache= */ true, context); | |
| // Assert | |
| ASSERT_EQ(FileEventMonitor::AwaitResult::Success, | |
| monitorCreateResult.monitor->awaitWriteOrTimeout()); | |
| auto result = | |
| PersistentGraphicsCacheTestUtils::newCache(file->path()).load(*getPipelineCacheKey()); | |
| ASSERT_NE(nullptr, result); | |
| ASSERT_NE(nullptr, result->data()); | |
| ASSERT_GT(result->size(), 0); | |
| } | |
| RENDERTHREAD_TEST(PersistentGraphicsCacheTest, | |
| hasPipelineCreationCacheControl_oldCache_isNotStored) { | |
| // Arrange | |
| ENSURE_FLAG_ENABLED(); | |
| VK_ONLY(); | |
| auto context = renderThread.getGrContext(); | |
| auto file = TestFile::ensureExistsEmpty(kFilename); | |
| ASSERT_TRUE(file.has_value()); | |
| auto& cache = PersistentGraphicsCacheTestUtils::newCache(file->path()); | |
| auto monitorCreateResult = FileEventMonitor::create(file->path()); | |
| ASSERT_SUCCESS(monitorCreateResult); | |
| // Act | |
| PersistentGraphicsCacheTestUtils::onVkFrameFlushed(cache, | |
| /* canDetectNewVkPipelineCacheData= */ true, | |
| /* hasNewPipelineCache= */ false, context); | |
| // Assert | |
| ASSERT_EQ(FileEventMonitor::AwaitResult::TimedOut, | |
| monitorCreateResult.monitor->awaitWriteOrTimeout()); | |
| auto result = | |
| PersistentGraphicsCacheTestUtils::newCache(file->path()).load(*getPipelineCacheKey()); | |
| ASSERT_EQ(nullptr, result); | |
| } | |
| RENDERTHREAD_TEST(PersistentGraphicsCacheTest, | |
| noPipelineCreationCacheControl_newCacheBySize_isStored) { | |
| // Arrange | |
| ENSURE_FLAG_ENABLED(); | |
| VK_ONLY(); | |
| auto context = renderThread.getGrContext(); | |
| auto file = TestFile::ensureExistsEmpty(kFilename); | |
| ASSERT_TRUE(file.has_value()); | |
| auto& cache = PersistentGraphicsCacheTestUtils::newCache(file->path()); | |
| auto monitorCreateResult = FileEventMonitor::create(file->path()); | |
| ASSERT_SUCCESS(monitorCreateResult); | |
| // Act | |
| // Current cache size is 0, so cache is new by size | |
| PersistentGraphicsCacheTestUtils::onVkFrameFlushed(cache, | |
| /* canDetectNewVkPipelineCacheData= */ false, | |
| /* hasNewPipelineCache= */ true, context); | |
| // Assert | |
| ASSERT_EQ(FileEventMonitor::AwaitResult::Success, | |
| monitorCreateResult.monitor->awaitWriteOrTimeout()); | |
| auto result = | |
| PersistentGraphicsCacheTestUtils::newCache(file->path()).load(*getPipelineCacheKey()); | |
| ASSERT_NE(nullptr, result); | |
| ASSERT_NE(nullptr, result->data()); | |
| ASSERT_GT(result->size(), 0); | |
| } | |
| RENDERTHREAD_TEST(PersistentGraphicsCacheTest, | |
| noPipelineCreationCacheControl_oldCacheBySize_isNotStored) { | |
| // Arrange | |
| ENSURE_FLAG_ENABLED(); | |
| VK_ONLY(); | |
| auto context = renderThread.getGrContext(); | |
| auto file = TestFile::ensureExistsEmpty(kFilename); | |
| ASSERT_TRUE(file.has_value()); | |
| auto& cache = PersistentGraphicsCacheTestUtils::newCache(file->path()); | |
| auto monitorCreateResult = FileEventMonitor::create(file->path()); | |
| ASSERT_SUCCESS(monitorCreateResult); | |
| // Current cache size is 0, so cache is new by size | |
| PersistentGraphicsCacheTestUtils::onVkFrameFlushed(cache, | |
| /* canDetectNewVkPipelineCacheData= */ false, | |
| /* hasNewPipelineCache= */ true, context); | |
| ASSERT_EQ(FileEventMonitor::AwaitResult::Success, | |
| monitorCreateResult.monitor->awaitWriteOrTimeout()); | |
| // Act | |
| // Cache size has not changed, so cache is old by size | |
| PersistentGraphicsCacheTestUtils::onVkFrameFlushed(cache, | |
| /* canDetectNewVkPipelineCacheData= */ false, | |
| /* hasNewPipelineCache= */ true, context); | |
| // Assert | |
| ASSERT_EQ(FileEventMonitor::AwaitResult::TimedOut, | |
| monitorCreateResult.monitor->awaitWriteOrTimeout()); | |
| } |