Source code
Revision control
Copy as Markdown
Other Tools
//
// Copyright 2018 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// BlobCache: Stores keyed blobs in memory to support EGL_ANDROID_blob_cache.
// Can be used in conjunction with the platform layer to warm up the cache from
// disk. MemoryProgramCache uses this to handle caching of compiled programs.
#include "libANGLE/BlobCache.h"
#include "common/utilities.h"
#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/histogram_macros.h"
#include "platform/PlatformMethods.h"
#define USE_SYSTEM_ZLIB
#include "compression_utils_portable.h"
namespace egl
{
namespace
{
enum CacheResult
{
kCacheMiss,
kCacheHitMemory,
kCacheHitDisk,
kCacheResultMax,
};
} // anonymous namespace
// In oder to store more cache in blob cache, compress cacheData to compressedData
// before being stored.
bool CompressBlobCacheData(const size_t cacheSize,
const uint8_t *cacheData,
angle::MemoryBuffer *compressedData)
{
uLong uncompressedSize = static_cast<uLong>(cacheSize);
uLong expectedCompressedSize = zlib_internal::GzipExpectedCompressedSize(uncompressedSize);
// Allocate memory.
if (!compressedData->resize(expectedCompressedSize))
{
ERR() << "Failed to allocate memory for compression";
return false;
}
int zResult = zlib_internal::GzipCompressHelper(compressedData->data(), &expectedCompressedSize,
cacheData, uncompressedSize, nullptr, nullptr);
if (zResult != Z_OK)
{
ERR() << "Failed to compress cache data: " << zResult;
return false;
}
// Resize it to expected size.
if (!compressedData->resize(expectedCompressedSize))
{
return false;
}
return true;
}
bool DecompressBlobCacheData(const uint8_t *compressedData,
const size_t compressedSize,
angle::MemoryBuffer *uncompressedData)
{
// Call zlib function to decompress.
uint32_t uncompressedSize =
zlib_internal::GetGzipUncompressedSize(compressedData, compressedSize);
// Allocate enough memory.
if (!uncompressedData->resize(uncompressedSize))
{
ERR() << "Failed to allocate memory for decompression";
return false;
}
uLong destLen = uncompressedSize;
int zResult = zlib_internal::GzipUncompressHelper(
uncompressedData->data(), &destLen, compressedData, static_cast<uLong>(compressedSize));
if (zResult != Z_OK)
{
ERR() << "Failed to decompress data: " << zResult << "\n";
return false;
}
// Resize it to expected size.
if (!uncompressedData->resize(destLen))
{
return false;
}
return true;
}
BlobCache::BlobCache(size_t maxCacheSizeBytes)
: mBlobCache(maxCacheSizeBytes), mSetBlobFunc(nullptr), mGetBlobFunc(nullptr)
{}
BlobCache::~BlobCache() {}
void BlobCache::put(const BlobCache::Key &key, angle::MemoryBuffer &&value)
{
if (areBlobCacheFuncsSet())
{
std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
// Store the result in the application's cache
mSetBlobFunc(key.data(), key.size(), value.data(), value.size());
}
else
{
populate(key, std::move(value), CacheSource::Memory);
}
}
bool BlobCache::compressAndPut(const BlobCache::Key &key,
angle::MemoryBuffer &&uncompressedValue,
size_t *compressedSize)
{
angle::MemoryBuffer compressedValue;
if (!CompressBlobCacheData(uncompressedValue.size(), uncompressedValue.data(),
&compressedValue))
{
return false;
}
if (compressedSize != nullptr)
*compressedSize = compressedValue.size();
put(key, std::move(compressedValue));
return true;
}
void BlobCache::putApplication(const BlobCache::Key &key, const angle::MemoryBuffer &value)
{
if (areBlobCacheFuncsSet())
{
std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
mSetBlobFunc(key.data(), key.size(), value.data(), value.size());
}
}
void BlobCache::populate(const BlobCache::Key &key, angle::MemoryBuffer &&value, CacheSource source)
{
std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
CacheEntry newEntry;
newEntry.first = std::move(value);
newEntry.second = source;
// Cache it inside blob cache only if caching inside the application is not possible.
mBlobCache.put(key, std::move(newEntry), newEntry.first.size());
}
bool BlobCache::get(angle::ScratchBuffer *scratchBuffer,
const BlobCache::Key &key,
BlobCache::Value *valueOut,
size_t *bufferSizeOut)
{
// Look into the application's cache, if there is such a cache
if (areBlobCacheFuncsSet())
{
std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
EGLsizeiANDROID valueSize = mGetBlobFunc(key.data(), key.size(), nullptr, 0);
if (valueSize <= 0)
{
return false;
}
angle::MemoryBuffer *scratchMemory;
bool result = scratchBuffer->get(valueSize, &scratchMemory);
if (!result)
{
ERR() << "Failed to allocate memory for binary blob";
return false;
}
EGLsizeiANDROID originalValueSize = valueSize;
valueSize = mGetBlobFunc(key.data(), key.size(), scratchMemory->data(), valueSize);
// Make sure the key/value pair still exists/is unchanged after the second call
// (modifications to the application cache by another thread are a possibility)
if (valueSize != originalValueSize)
{
// This warning serves to find issues with the application cache, none of which are
// currently known to be thread-safe. If such a use ever arises, this WARN can be
// removed.
WARN() << "Binary blob no longer available in cache (removed by a thread?)";
return false;
}
*valueOut = BlobCache::Value(scratchMemory->data(), scratchMemory->size());
*bufferSizeOut = valueSize;
return true;
}
std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
// Otherwise we are doing caching internally, so try to find it there
const CacheEntry *entry;
bool result = mBlobCache.get(key, &entry);
if (result)
{
*valueOut = BlobCache::Value(entry->first.data(), entry->first.size());
*bufferSizeOut = entry->first.size();
}
return result;
}
bool BlobCache::getAt(size_t index, const BlobCache::Key **keyOut, BlobCache::Value *valueOut)
{
std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
const CacheEntry *valueBuf;
bool result = mBlobCache.getAt(index, keyOut, &valueBuf);
if (result)
{
*valueOut = BlobCache::Value(valueBuf->first.data(), valueBuf->first.size());
}
return result;
}
BlobCache::GetAndDecompressResult BlobCache::getAndDecompress(
angle::ScratchBuffer *scratchBuffer,
const BlobCache::Key &key,
angle::MemoryBuffer *uncompressedValueOut)
{
ASSERT(uncompressedValueOut);
Value compressedValue;
size_t compressedSize;
if (!get(scratchBuffer, key, &compressedValue, &compressedSize))
{
return GetAndDecompressResult::NotFound;
}
{
// This needs to be locked because `DecompressBlobCacheData` is reading shared memory from
// `compressedValue.data()`.
std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
if (!DecompressBlobCacheData(compressedValue.data(), compressedSize, uncompressedValueOut))
{
return GetAndDecompressResult::DecompressFailure;
}
}
return GetAndDecompressResult::GetSuccess;
}
void BlobCache::remove(const BlobCache::Key &key)
{
std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
mBlobCache.eraseByKey(key);
}
void BlobCache::setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get)
{
std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
mSetBlobFunc = set;
mGetBlobFunc = get;
}
bool BlobCache::areBlobCacheFuncsSet() const
{
std::scoped_lock<std::mutex> lock(mBlobCacheMutex);
// Either none or both of the callbacks should be set.
ASSERT((mSetBlobFunc != nullptr) == (mGetBlobFunc != nullptr));
return mSetBlobFunc != nullptr && mGetBlobFunc != nullptr;
}
} // namespace egl