blob: 4c5fe64f59b040a1a232c551dfd140335e0df5e7 [file] [log] [blame]
/*
* 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());
}