Commit cc5083e0 by Amy Liu Committed by Commit Bot

Compression of the data from vkGetPipelineCacheData.

The size of pipelineCacheData sometimes is greater than 64k which cannot be saved because of the Android blob cache limitation (single cache data size should be < 64k). Implement the compression to store more cache data. Bug: angleproject:4722 Change-Id: I435b086d70d0e6378f1141464ae2bafbe076f193 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2631511 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarShahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org>
parent cfb9c30c
...@@ -14,6 +14,9 @@ ...@@ -14,6 +14,9 @@
#include "libANGLE/histogram_macros.h" #include "libANGLE/histogram_macros.h"
#include "platform/PlatformMethods.h" #include "platform/PlatformMethods.h"
#define USE_SYSTEM_ZLIB
#include "compression_utils_portable.h"
namespace egl namespace egl
{ {
...@@ -29,6 +32,73 @@ enum CacheResult ...@@ -29,6 +32,73 @@ enum CacheResult
} // anonymous namespace } // anonymous namespace
// In oder to store more cache in blob cache, compress cacheData to compressedData
// before being stored.
bool CompressBlobCacheData(angle::MemoryBuffer *cacheData, angle::MemoryBuffer *compressedData)
{
uLong uncompressedSize = static_cast<uLong>(cacheData->size());
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->data(), 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) BlobCache::BlobCache(size_t maxCacheSizeBytes)
: mBlobCache(maxCacheSizeBytes), mSetBlobFunc(nullptr), mGetBlobFunc(nullptr) : mBlobCache(maxCacheSizeBytes), mSetBlobFunc(nullptr), mGetBlobFunc(nullptr)
{} {}
......
...@@ -48,6 +48,11 @@ struct hash<egl::BlobCacheKey> ...@@ -48,6 +48,11 @@ struct hash<egl::BlobCacheKey>
namespace egl namespace egl
{ {
bool CompressBlobCacheData(angle::MemoryBuffer *cacheData, angle::MemoryBuffer *compressedData);
bool DecompressBlobCacheData(const uint8_t *compressedData,
const size_t compressedSize,
angle::MemoryBuffer *uncompressedData);
class BlobCache final : angle::NonCopyable class BlobCache final : angle::NonCopyable
{ {
public: public:
......
...@@ -21,9 +21,6 @@ ...@@ -21,9 +21,6 @@
#include "libANGLE/renderer/ProgramImpl.h" #include "libANGLE/renderer/ProgramImpl.h"
#include "platform/PlatformMethods.h" #include "platform/PlatformMethods.h"
#define USE_SYSTEM_ZLIB
#include "compression_utils_portable.h"
namespace gl namespace gl
{ {
...@@ -146,18 +143,10 @@ angle::Result MemoryProgramCache::getProgram(const Context *context, ...@@ -146,18 +143,10 @@ angle::Result MemoryProgramCache::getProgram(const Context *context,
size_t programSize = 0; size_t programSize = 0;
if (get(context, *hashOut, &binaryProgram, &programSize)) if (get(context, *hashOut, &binaryProgram, &programSize))
{ {
uint32_t uncompressedSize = angle::MemoryBuffer uncompressedData;
zlib_internal::GetGzipUncompressedSize(binaryProgram.data(), programSize); if (!egl::DecompressBlobCacheData(binaryProgram.data(), programSize, &uncompressedData))
std::vector<uint8_t> uncompressedData(uncompressedSize);
uLong destLen = uncompressedSize;
int zResult = zlib_internal::GzipUncompressHelper(uncompressedData.data(), &destLen,
binaryProgram.data(),
static_cast<uLong>(programSize));
if (zResult != Z_OK)
{ {
ERR() << "Failure to decompressed binary data: " << zResult << "\n"; ERR() << "Error decompressing binary data.";
return angle::Result::Incomplete; return angle::Result::Incomplete;
} }
...@@ -220,33 +209,10 @@ angle::Result MemoryProgramCache::putProgram(const egl::BlobCache::Key &programH ...@@ -220,33 +209,10 @@ angle::Result MemoryProgramCache::putProgram(const egl::BlobCache::Key &programH
angle::MemoryBuffer serializedProgram; angle::MemoryBuffer serializedProgram;
ANGLE_TRY(program->serialize(context, &serializedProgram)); ANGLE_TRY(program->serialize(context, &serializedProgram));
// Compress the program data
uLong uncompressedSize = static_cast<uLong>(serializedProgram.size());
uLong expectedCompressedSize = zlib_internal::GzipExpectedCompressedSize(uncompressedSize);
angle::MemoryBuffer compressedData; angle::MemoryBuffer compressedData;
if (!compressedData.resize(expectedCompressedSize)) if (!egl::CompressBlobCacheData(&serializedProgram, &compressedData))
{
ERR() << "Failed to allocate enough memory to hold compressed program. ("
<< expectedCompressedSize << " bytes )";
return angle::Result::Incomplete;
}
int zResult = zlib_internal::GzipCompressHelper(compressedData.data(), &expectedCompressedSize,
serializedProgram.data(), uncompressedSize,
nullptr, nullptr);
if (zResult != Z_OK)
{
FATAL() << "Error compressing binary data: " << zResult;
return angle::Result::Incomplete;
}
// Resize the buffer to the actual compressed size
if (!compressedData.resize(expectedCompressedSize))
{ {
ERR() << "Failed to resize to actual compressed program size. (" << expectedCompressedSize ERR() << "Error compressing binary data.";
<< " bytes )";
return angle::Result::Incomplete; return angle::Result::Incomplete;
} }
......
...@@ -457,6 +457,147 @@ ANGLE_MAYBE_UNUSED bool SemaphorePropertiesCompatibleWithAndroid( ...@@ -457,6 +457,147 @@ ANGLE_MAYBE_UNUSED bool SemaphorePropertiesCompatibleWithAndroid(
return true; return true;
} }
void ComputePipelineCacheVkChunkKey(VkPhysicalDeviceProperties physicalDeviceProperties,
const uint8_t chunkIndex,
egl::BlobCache::Key *hashOut)
{
std::ostringstream hashStream("ANGLE Pipeline Cache: ", std::ios_base::ate);
// Add the pipeline cache UUID to make sure the blob cache always gives a compatible pipeline
// cache. It's not particularly necessary to write it as a hex number as done here, so long as
// there is no '\0' in the result.
for (const uint32_t c : physicalDeviceProperties.pipelineCacheUUID)
{
hashStream << std::hex << c;
}
// Add the vendor and device id too for good measure.
hashStream << std::hex << physicalDeviceProperties.vendorID;
hashStream << std::hex << physicalDeviceProperties.deviceID;
// Add chunkIndex to generate unique key for chunks.
hashStream << std::hex << chunkIndex;
const std::string &hashString = hashStream.str();
angle::base::SHA1HashBytes(reinterpret_cast<const unsigned char *>(hashString.c_str()),
hashString.length(), hashOut->data());
}
angle::Result CompressAndStorePipelineCacheVk(VkPhysicalDeviceProperties physicalDeviceProperties,
DisplayVk *displayVk,
angle::MemoryBuffer *pipelineCacheData,
bool *success)
{
// Compress the whole pipelineCache.
angle::MemoryBuffer compressedData;
ANGLE_VK_CHECK(displayVk, egl::CompressBlobCacheData(pipelineCacheData, &compressedData),
VK_ERROR_INITIALIZATION_FAILED);
// If the size of compressedData is larger than (kMaxBlobCacheSize - sizeof(numChunks)),
// the pipelineCache still can't be stored in blob cache. Divide the large compressed
// pipelineCache into several parts to store seperately. There is no function to
// query the limit size in android.
constexpr size_t kMaxBlobCacheSize = 64 * 1024;
// Store {numChunks, chunkCompressedData} in keyData, numChunks is used to validate the data.
// For example, if the compressed size is 68841 bytes(67k), divide into {2,34421 bytes} and
// {2,34420 bytes}.
constexpr size_t kBlobHeaderSize = sizeof(uint8_t);
size_t compressedOffset = 0;
const size_t numChunks = UnsignedCeilDivide(static_cast<unsigned int>(compressedData.size()),
kMaxBlobCacheSize - kBlobHeaderSize);
size_t chunkSize = UnsignedCeilDivide(static_cast<unsigned int>(compressedData.size()),
static_cast<unsigned int>(numChunks));
for (size_t chunkIndex = 0; chunkIndex < numChunks; ++chunkIndex)
{
if (chunkIndex == numChunks - 1)
{
chunkSize = compressedData.size() - compressedOffset;
}
angle::MemoryBuffer keyData;
ANGLE_VK_CHECK(displayVk, keyData.resize(kBlobHeaderSize + chunkSize),
VK_ERROR_INITIALIZATION_FAILED);
ASSERT(numChunks <= UINT8_MAX);
keyData.data()[0] = static_cast<uint8_t>(numChunks);
memcpy(keyData.data() + kBlobHeaderSize, compressedData.data() + compressedOffset,
chunkSize);
compressedOffset += chunkSize;
// Create unique hash key.
egl::BlobCache::Key chunkCacheHash;
ComputePipelineCacheVkChunkKey(physicalDeviceProperties, chunkIndex, &chunkCacheHash);
displayVk->getBlobCache()->putApplication(chunkCacheHash, keyData);
}
*success = true;
return angle::Result::Continue;
}
angle::Result GetAndDecompressPipelineCacheVk(VkPhysicalDeviceProperties physicalDeviceProperties,
DisplayVk *displayVk,
angle::MemoryBuffer *uncompressedData,
bool *success)
{
// Compute the hash key of chunkIndex 0 and find the first cache data in blob cache.
egl::BlobCache::Key chunkCacheHash;
ComputePipelineCacheVkChunkKey(physicalDeviceProperties, 0, &chunkCacheHash);
egl::BlobCache::Value keyData;
size_t keySize = 0;
constexpr size_t kBlobHeaderSize = sizeof(uint8_t);
if (!displayVk->getBlobCache()->get(displayVk->getScratchBuffer(), chunkCacheHash, &keyData,
&keySize) ||
keyData.size() < kBlobHeaderSize)
{
return angle::Result::Continue;
}
// Get the number of chunks.
size_t numChunks = keyData.data()[0];
size_t chunkSize = keySize - kBlobHeaderSize;
size_t compressedSize = 0;
// Allocate enough memory.
angle::MemoryBuffer compressedData;
ANGLE_VK_CHECK(displayVk, compressedData.resize(chunkSize * numChunks),
VK_ERROR_INITIALIZATION_FAILED);
// To combine the parts of the pipelineCache data.
for (size_t chunkIndex = 0; chunkIndex < numChunks; ++chunkIndex)
{
// Get the unique key by chunkIndex.
ComputePipelineCacheVkChunkKey(physicalDeviceProperties, chunkIndex, &chunkCacheHash);
if (!displayVk->getBlobCache()->get(displayVk->getScratchBuffer(), chunkCacheHash, &keyData,
&keySize) ||
keyData.size() < kBlobHeaderSize)
{
// Can't find every part of the cache data.
return angle::Result::Continue;
}
size_t checkNumber = keyData.data()[0];
chunkSize = keySize - kBlobHeaderSize;
if (checkNumber != numChunks || compressedData.size() < (compressedSize + chunkSize))
{
// Validate the number value and enough space to store.
return angle::Result::Continue;
}
memcpy(compressedData.data() + compressedSize, keyData.data() + kBlobHeaderSize, chunkSize);
compressedSize += chunkSize;
}
ANGLE_VK_CHECK(
displayVk,
egl::DecompressBlobCacheData(compressedData.data(), compressedSize, uncompressedData),
VK_ERROR_INITIALIZATION_FAILED);
*success = true;
return angle::Result::Continue;
}
// Environment variable (and associated Android property) to enable Vulkan debug-utils markers // Environment variable (and associated Android property) to enable Vulkan debug-utils markers
constexpr char kEnableDebugMarkersVarName[] = "ANGLE_ENABLE_DEBUG_MARKERS"; constexpr char kEnableDebugMarkersVarName[] = "ANGLE_ENABLE_DEBUG_MARKERS";
constexpr char kEnableDebugMarkersPropertyName[] = "debug.angle.markers"; constexpr char kEnableDebugMarkersPropertyName[] = "debug.angle.markers";
...@@ -2062,41 +2203,19 @@ void RendererVk::initFeatures(DisplayVk *displayVk, ...@@ -2062,41 +2203,19 @@ void RendererVk::initFeatures(DisplayVk *displayVk,
ApplyFeatureOverrides(&mFeatures, displayVk->getState()); ApplyFeatureOverrides(&mFeatures, displayVk->getState());
} }
void RendererVk::initPipelineCacheVkKey()
{
std::ostringstream hashStream("ANGLE Pipeline Cache: ", std::ios_base::ate);
// Add the pipeline cache UUID to make sure the blob cache always gives a compatible pipeline
// cache. It's not particularly necessary to write it as a hex number as done here, so long as
// there is no '\0' in the result.
for (const uint32_t c : mPhysicalDeviceProperties.pipelineCacheUUID)
{
hashStream << std::hex << c;
}
// Add the vendor and device id too for good measure.
hashStream << std::hex << mPhysicalDeviceProperties.vendorID;
hashStream << std::hex << mPhysicalDeviceProperties.deviceID;
const std::string &hashString = hashStream.str();
angle::base::SHA1HashBytes(reinterpret_cast<const unsigned char *>(hashString.c_str()),
hashString.length(), mPipelineCacheVkBlobKey.data());
}
angle::Result RendererVk::initPipelineCache(DisplayVk *display, angle::Result RendererVk::initPipelineCache(DisplayVk *display,
vk::PipelineCache *pipelineCache, vk::PipelineCache *pipelineCache,
bool *success) bool *success)
{ {
initPipelineCacheVkKey(); angle::MemoryBuffer initialData;
ANGLE_TRY(
egl::BlobCache::Value initialData; GetAndDecompressPipelineCacheVk(mPhysicalDeviceProperties, display, &initialData, success));
size_t dataSize = 0;
*success = display->getBlobCache()->get(display->getScratchBuffer(), mPipelineCacheVkBlobKey,
&initialData, &dataSize);
VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {}; VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
pipelineCacheCreateInfo.flags = 0; pipelineCacheCreateInfo.flags = 0;
pipelineCacheCreateInfo.initialDataSize = *success ? dataSize : 0; pipelineCacheCreateInfo.initialDataSize = *success ? initialData.size() : 0;
pipelineCacheCreateInfo.pInitialData = *success ? initialData.data() : nullptr; pipelineCacheCreateInfo.pInitialData = *success ? initialData.data() : nullptr;
ANGLE_VK_TRY(display, pipelineCache->init(mDevice, pipelineCacheCreateInfo)); ANGLE_VK_TRY(display, pipelineCache->init(mDevice, pipelineCacheCreateInfo));
...@@ -2231,8 +2350,14 @@ angle::Result RendererVk::syncPipelineCacheVk(DisplayVk *displayVk) ...@@ -2231,8 +2350,14 @@ angle::Result RendererVk::syncPipelineCacheVk(DisplayVk *displayVk)
pipelineCacheData->size() - pipelineCacheSize); pipelineCacheData->size() - pipelineCacheSize);
} }
displayVk->getBlobCache()->putApplication(mPipelineCacheVkBlobKey, *pipelineCacheData); bool success = false;
mPipelineCacheDirty = false; ANGLE_TRY(CompressAndStorePipelineCacheVk(mPhysicalDeviceProperties, displayVk,
pipelineCacheData, &success));
if (success)
{
mPipelineCacheDirty = false;
}
return angle::Result::Continue; return angle::Result::Continue;
} }
......
...@@ -368,7 +368,6 @@ class RendererVk : angle::NonCopyable ...@@ -368,7 +368,6 @@ class RendererVk : angle::NonCopyable
void queryDeviceExtensionFeatures(const vk::ExtensionNameList &deviceExtensionNames); void queryDeviceExtensionFeatures(const vk::ExtensionNameList &deviceExtensionNames);
void initFeatures(DisplayVk *display, const vk::ExtensionNameList &extensions); void initFeatures(DisplayVk *display, const vk::ExtensionNameList &extensions);
void initPipelineCacheVkKey();
angle::Result initPipelineCache(DisplayVk *display, angle::Result initPipelineCache(DisplayVk *display,
vk::PipelineCache *pipelineCache, vk::PipelineCache *pipelineCache,
bool *success); bool *success);
...@@ -436,7 +435,6 @@ class RendererVk : angle::NonCopyable ...@@ -436,7 +435,6 @@ class RendererVk : angle::NonCopyable
// a lock. // a lock.
std::mutex mPipelineCacheMutex; std::mutex mPipelineCacheMutex;
vk::PipelineCache mPipelineCache; vk::PipelineCache mPipelineCache;
egl::BlobCache::Key mPipelineCacheVkBlobKey;
uint32_t mPipelineCacheVkUpdateTimeout; uint32_t mPipelineCacheVkUpdateTimeout;
bool mPipelineCacheDirty; bool mPipelineCacheDirty;
bool mPipelineCacheInitialized; bool mPipelineCacheInitialized;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment