Commit 9137adea by Shahbaz Youssefi Committed by Commit Bot

Add support for EGL_ANDROID_blob_cache

The functionality of MemoryProgramCache is divided up in two. BlobCache is now a generic binary cache, which interfaces with the callbacks from EGL_ANDROID_blob_cache. MemoryProgramCache handles program [de]serialization and interacts with BlobCache. Bug: angleproject:2516 Change-Id: Ie4328a2e56a26338e033d84f4e53a1103411937d Reviewed-on: https://chromium-review.googlesource.com/1194285 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org>
parent 913ff54d
...@@ -37,6 +37,17 @@ class MemoryBuffer final : NonCopyable ...@@ -37,6 +37,17 @@ class MemoryBuffer final : NonCopyable
return mData; return mData;
} }
uint8_t &operator[](size_t pos)
{
ASSERT(pos < mSize);
return mData[pos];
}
const uint8_t &operator[](size_t pos) const
{
ASSERT(pos < mSize);
return mData[pos];
}
void fill(uint8_t datum); void fill(uint8_t datum);
private: private:
......
//
// 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 "common/version.h"
#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/histogram_macros.h"
#include "platform/Platform.h"
namespace egl
{
namespace
{
enum CacheResult
{
kCacheMiss,
kCacheHitMemory,
kCacheHitDisk,
kCacheResultMax,
};
} // anonymous namespace
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())
{
// 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);
}
}
void BlobCache::populate(const BlobCache::Key &key, angle::MemoryBuffer &&value, CacheSource source)
{
CacheEntry newEntry;
newEntry.first = std::move(value);
newEntry.second = source;
// Cache it inside blob cache only if caching inside the application is not possible.
const CacheEntry *result = mBlobCache.put(key, std::move(newEntry), newEntry.first.size());
if (!result)
{
ERR() << "Failed to store binary blob in memory cache, blob is too large.";
}
}
bool BlobCache::get(const gl::Context *context,
const BlobCache::Key &key,
BlobCache::Value *valueOut)
{
// Look into the application's cache, if there is such a cache
if (areBlobCacheFuncsSet())
{
EGLsizeiANDROID valueSize = mGetBlobFunc(key.data(), key.size(), nullptr, 0);
if (valueSize <= 0)
{
return false;
}
angle::MemoryBuffer *scratchBuffer;
bool result = context->getScratchBuffer(valueSize, &scratchBuffer);
if (!result)
{
ERR() << "Failed to allocate memory for binary blob";
return false;
}
valueSize = mGetBlobFunc(key.data(), key.size(), scratchBuffer->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 (static_cast<size_t>(valueSize) != scratchBuffer->size())
{
// 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(scratchBuffer->data(), scratchBuffer->size());
return true;
}
// Otherwise we are doing caching internally, so try to find it there
const CacheEntry *entry;
bool result = mBlobCache.get(key, &entry);
if (result)
{
if (entry->second == CacheSource::Memory)
{
ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheHitMemory,
kCacheResultMax);
}
else
{
ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheHitDisk,
kCacheResultMax);
}
*valueOut = BlobCache::Value(entry->first.data(), entry->first.size());
}
else
{
ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheMiss,
kCacheResultMax);
}
return result;
}
bool BlobCache::getAt(size_t index, const BlobCache::Key **keyOut, BlobCache::Value *valueOut)
{
const CacheEntry *valueBuf;
bool result = mBlobCache.getAt(index, keyOut, &valueBuf);
if (result)
{
*valueOut = BlobCache::Value(valueBuf->first.data(), valueBuf->first.size());
}
return result;
}
void BlobCache::remove(const BlobCache::Key &key)
{
bool result = mBlobCache.eraseByKey(key);
ASSERT(result);
}
void BlobCache::setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get)
{
mSetBlobFunc = set;
mGetBlobFunc = get;
}
bool BlobCache::areBlobCacheFuncsSet() const
{
// Either none or both of the callbacks should be set.
ASSERT((mSetBlobFunc != nullptr) == (mGetBlobFunc != nullptr));
return mSetBlobFunc != nullptr && mGetBlobFunc != nullptr;
}
} // namespace gl
//
// 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 compiled and linked programs in memory so they don't
// always have to be re-compiled. Can be used in conjunction with the platform
// layer to warm up the cache from disk.
#ifndef LIBANGLE_BLOB_CACHE_H_
#define LIBANGLE_BLOB_CACHE_H_
#include <array>
#include <cstring>
#include <anglebase/sha1.h>
#include "common/MemoryBuffer.h"
#include "libANGLE/Error.h"
#include "libANGLE/SizedMRUCache.h"
namespace gl
{
class Context;
} // namespace gl
namespace egl
{
// 160-bit SHA-1 hash key used for hasing a program. BlobCache opts in using fixed keys for
// simplicity and efficiency.
static constexpr size_t kBlobCacheKeyLength = angle::base::kSHA1Length;
using BlobCacheKey = std::array<uint8_t, kBlobCacheKeyLength>;
} // namespace egl
namespace std
{
template <>
struct hash<egl::BlobCacheKey>
{
// Simple routine to hash four ints.
size_t operator()(const egl::BlobCacheKey &key) const
{
return angle::ComputeGenericHash(key.data(), key.size());
}
};
} // namespace std
namespace egl
{
class BlobCache final : angle::NonCopyable
{
public:
// 160-bit SHA-1 hash key used for hasing a program. BlobCache opts in using fixed keys for
// simplicity and efficiency.
static constexpr size_t kKeyLength = kBlobCacheKeyLength;
using Key = BlobCacheKey;
class Value
{
public:
Value() : mPtr(nullptr), mSize(0) {}
Value(const uint8_t *ptr, size_t sz) : mPtr(ptr), mSize(sz) {}
// A very basic struct to hold the pointer and size together. The objects of this class
// doesn't own the memory.
const uint8_t *data() { return mPtr; }
size_t size() { return mSize; }
const uint8_t &operator[](size_t pos) const
{
ASSERT(pos < mSize);
return mPtr[pos];
}
private:
const uint8_t *mPtr;
size_t mSize;
};
enum class CacheSource
{
Memory,
Disk,
};
explicit BlobCache(size_t maxCacheSizeBytes);
~BlobCache();
// Store a key-blob pair in the cache. If application callbacks are set, the application cache
// will be used. Otherwise the value is cached in this object.
void put(const BlobCache::Key &key, angle::MemoryBuffer &&value);
// Store a key-blob pair in the cache without making callbacks to the application. This is used
// to repopulate this object's cache on startup without generating callback calls.
void populate(const BlobCache::Key &key,
angle::MemoryBuffer &&value,
CacheSource source = CacheSource::Disk);
// Check if the cache contains the blob corresponding to this key. If application callbacks are
// set, those will be used. Otherwise they key is looked up in this object's cache.
bool get(const gl::Context *context, const BlobCache::Key &key, BlobCache::Value *valueOut);
// For querying the contents of the cache.
bool getAt(size_t index, const BlobCache::Key **keyOut, BlobCache::Value *valueOut);
// Evict a blob from the binary cache.
void remove(const BlobCache::Key &key);
// Empty the cache.
void clear() { mBlobCache.clear(); }
// Resize the cache. Discards current contents.
void resize(size_t maxCacheSizeBytes) { mBlobCache.resize(maxCacheSizeBytes); }
// Returns the number of entries in the cache.
size_t entryCount() const { return mBlobCache.entryCount(); }
// Reduces the current cache size and returns the number of bytes freed.
size_t trim(size_t limit) { return mBlobCache.shrinkToSize(limit); }
// Returns the current cache size in bytes.
size_t size() const { return mBlobCache.size(); }
// Returns whether the cache is empty
bool empty() const { return mBlobCache.empty(); }
// Returns the maximum cache size in bytes.
size_t maxSize() const { return mBlobCache.maxSize(); }
void setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get);
bool areBlobCacheFuncsSet() const;
private:
// This internal cache is used only if the application is not providing caching callbacks
using CacheEntry = std::pair<angle::MemoryBuffer, CacheSource>;
angle::SizedMRUCache<BlobCache::Key, CacheEntry> mBlobCache;
EGLSetBlobFuncANDROID mSetBlobFunc;
EGLGetBlobFuncANDROID mGetBlobFunc;
};
} // namespace egl
#endif // LIBANGLE_MEMORY_PROGRAM_CACHE_H_
//
// 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_unittest.h: Unit tests for the blob cache.
#include <gtest/gtest.h>
#include "libANGLE/BlobCache.h"
namespace egl
{
// Note: this is fairly similar to SizedMRUCache_unittest, and makes sure the
// BlobCache usage of SizedMRUCache is not broken.
using BlobPut = angle::MemoryBuffer;
using Blob = BlobCache::Value;
using Key = BlobCache::Key;
template <typename T>
void MakeSequence(T &seq, uint8_t start)
{
for (uint8_t i = 0; i < seq.size(); ++i)
{
seq[i] = i + start;
}
}
BlobPut MakeBlob(size_t size, uint8_t start = 0)
{
BlobPut blob;
blob.resize(size);
MakeSequence(blob, start);
return blob;
}
Key MakeKey(uint8_t start = 0)
{
Key key;
MakeSequence(key, start);
return key;
}
// Test a cache with a value that takes up maximum size.
TEST(BlobCacheTest, MaxSizedValue)
{
constexpr size_t kSize = 32;
BlobCache blobCache(kSize);
blobCache.populate(MakeKey(0), MakeBlob(kSize));
EXPECT_EQ(32u, blobCache.size());
EXPECT_FALSE(blobCache.empty());
blobCache.populate(MakeKey(1), MakeBlob(kSize));
EXPECT_EQ(32u, blobCache.size());
EXPECT_FALSE(blobCache.empty());
Blob blob;
EXPECT_FALSE(blobCache.get(nullptr, MakeKey(0), &blob));
blobCache.clear();
EXPECT_TRUE(blobCache.empty());
}
// Test a cache with many small values, that it can handle unlimited inserts.
TEST(BlobCacheTest, ManySmallValues)
{
constexpr size_t kSize = 32;
BlobCache blobCache(kSize);
for (size_t value = 0; value < kSize; ++value)
{
blobCache.populate(MakeKey(value), MakeBlob(1, value));
Blob qvalue;
EXPECT_TRUE(blobCache.get(nullptr, MakeKey(value), &qvalue));
if (qvalue.size() > 0)
{
EXPECT_EQ(value, qvalue[0]);
}
}
EXPECT_EQ(32u, blobCache.size());
EXPECT_FALSE(blobCache.empty());
// Putting one element evicts the first element.
blobCache.populate(MakeKey(kSize), MakeBlob(1, kSize));
Blob qvalue;
EXPECT_FALSE(blobCache.get(nullptr, MakeKey(0), &qvalue));
// Putting one large element cleans out the whole stack.
blobCache.populate(MakeKey(kSize + 1), MakeBlob(kSize, kSize + 1));
EXPECT_EQ(32u, blobCache.size());
EXPECT_FALSE(blobCache.empty());
for (size_t value = 0; value <= kSize; ++value)
{
EXPECT_FALSE(blobCache.get(nullptr, MakeKey(value), &qvalue));
}
EXPECT_TRUE(blobCache.get(nullptr, MakeKey(kSize + 1), &qvalue));
if (qvalue.size() > 0)
{
EXPECT_EQ(kSize + 1, qvalue[0]);
}
// Put a bunch of items in the cache sequentially.
for (size_t value = 0; value < kSize * 10; ++value)
{
blobCache.populate(MakeKey(value), MakeBlob(1, value));
}
EXPECT_EQ(32u, blobCache.size());
}
// Tests putting an oversize element.
TEST(BlobCacheTest, OversizeValue)
{
constexpr size_t kSize = 32;
BlobCache blobCache(kSize);
blobCache.populate(MakeKey(5), MakeBlob(100));
Blob qvalue;
EXPECT_FALSE(blobCache.get(nullptr, MakeKey(5), &qvalue));
}
} // namespace angle
...@@ -1318,7 +1318,8 @@ DisplayExtensions::DisplayExtensions() ...@@ -1318,7 +1318,8 @@ DisplayExtensions::DisplayExtensions()
robustResourceInitialization(false), robustResourceInitialization(false),
iosurfaceClientBuffer(false), iosurfaceClientBuffer(false),
createContextExtensionsEnabled(false), createContextExtensionsEnabled(false),
presentationTime(false) presentationTime(false),
blobCache(false)
{ {
} }
...@@ -1366,6 +1367,7 @@ std::vector<std::string> DisplayExtensions::getStrings() const ...@@ -1366,6 +1367,7 @@ std::vector<std::string> DisplayExtensions::getStrings() const
InsertExtensionString("EGL_ANGLE_iosurface_client_buffer", iosurfaceClientBuffer, &extensionStrings); InsertExtensionString("EGL_ANGLE_iosurface_client_buffer", iosurfaceClientBuffer, &extensionStrings);
InsertExtensionString("EGL_ANGLE_create_context_extensions_enabled", createContextExtensionsEnabled, &extensionStrings); InsertExtensionString("EGL_ANGLE_create_context_extensions_enabled", createContextExtensionsEnabled, &extensionStrings);
InsertExtensionString("EGL_ANDROID_presentation_time", presentationTime, &extensionStrings); InsertExtensionString("EGL_ANDROID_presentation_time", presentationTime, &extensionStrings);
InsertExtensionString("EGL_ANDROID_blob_cache", blobCache, &extensionStrings);
// TODO(jmadill): Enable this when complete. // TODO(jmadill): Enable this when complete.
//InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings); //InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings);
// clang-format on // clang-format on
......
...@@ -778,6 +778,9 @@ struct DisplayExtensions ...@@ -778,6 +778,9 @@ struct DisplayExtensions
// EGL_ANDROID_presentation_time // EGL_ANDROID_presentation_time
bool presentationTime; bool presentationTime;
// EGL_ANDROID_blob_cache
bool blobCache;
}; };
struct DeviceExtensions struct DeviceExtensions
......
...@@ -386,7 +386,8 @@ Display::Display(EGLenum platform, EGLNativeDisplayType displayId, Device *eglDe ...@@ -386,7 +386,8 @@ Display::Display(EGLenum platform, EGLNativeDisplayType displayId, Device *eglDe
mDevice(eglDevice), mDevice(eglDevice),
mPlatform(platform), mPlatform(platform),
mTextureManager(nullptr), mTextureManager(nullptr),
mMemoryProgramCache(gl::kDefaultMaxProgramCacheMemoryBytes), mBlobCache(gl::kDefaultMaxProgramCacheMemoryBytes),
mMemoryProgramCache(mBlobCache),
mGlobalTextureShareGroupUsers(0) mGlobalTextureShareGroupUsers(0)
{ {
} }
...@@ -542,6 +543,7 @@ Error Display::terminate(const Thread *thread) ...@@ -542,6 +543,7 @@ Error Display::terminate(const Thread *thread)
} }
mMemoryProgramCache.clear(); mMemoryProgramCache.clear();
mBlobCache.setBlobCacheFuncs(nullptr, nullptr);
while (!mContextSet.empty()) while (!mContextSet.empty())
{ {
...@@ -947,6 +949,12 @@ void Display::notifyDeviceLost() ...@@ -947,6 +949,12 @@ void Display::notifyDeviceLost()
mDeviceLost = true; mDeviceLost = true;
} }
void Display::setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get)
{
mBlobCache.setBlobCacheFuncs(set, get);
mImplementation->setBlobCacheFuncs(set, get);
}
Error Display::waitClient(const gl::Context *context) Error Display::waitClient(const gl::Context *context)
{ {
return mImplementation->waitClient(context); return mImplementation->waitClient(context);
...@@ -1092,6 +1100,9 @@ void Display::initDisplayExtensions() ...@@ -1092,6 +1100,9 @@ void Display::initDisplayExtensions()
// Request extension is implemented in the ANGLE frontend // Request extension is implemented in the ANGLE frontend
mDisplayExtensions.createContextExtensionsEnabled = true; mDisplayExtensions.createContextExtensionsEnabled = true;
// Blob cache extension is provided by the ANGLE frontend
mDisplayExtensions.blobCache = true;
mDisplayExtensionString = GenerateExtensionsString(mDisplayExtensions); mDisplayExtensionString = GenerateExtensionsString(mDisplayExtensions);
} }
...@@ -1186,7 +1197,7 @@ EGLint Display::programCacheGetAttrib(EGLenum attrib) const ...@@ -1186,7 +1197,7 @@ EGLint Display::programCacheGetAttrib(EGLenum attrib) const
switch (attrib) switch (attrib)
{ {
case EGL_PROGRAM_CACHE_KEY_LENGTH_ANGLE: case EGL_PROGRAM_CACHE_KEY_LENGTH_ANGLE:
return static_cast<EGLint>(gl::kProgramHashLength); return static_cast<EGLint>(BlobCache::kKeyLength);
case EGL_PROGRAM_CACHE_SIZE_ANGLE: case EGL_PROGRAM_CACHE_SIZE_ANGLE:
return static_cast<EGLint>(mMemoryProgramCache.entryCount()); return static_cast<EGLint>(mMemoryProgramCache.entryCount());
...@@ -1205,8 +1216,8 @@ Error Display::programCacheQuery(EGLint index, ...@@ -1205,8 +1216,8 @@ Error Display::programCacheQuery(EGLint index,
{ {
ASSERT(index >= 0 && index < static_cast<EGLint>(mMemoryProgramCache.entryCount())); ASSERT(index >= 0 && index < static_cast<EGLint>(mMemoryProgramCache.entryCount()));
const angle::MemoryBuffer *programBinary = nullptr; const BlobCache::Key *programHash = nullptr;
gl::ProgramHash programHash; BlobCache::Value programBinary;
// TODO(jmadill): Make this thread-safe. // TODO(jmadill): Make this thread-safe.
bool result = bool result =
mMemoryProgramCache.getAt(static_cast<size_t>(index), &programHash, &programBinary); mMemoryProgramCache.getAt(static_cast<size_t>(index), &programHash, &programBinary);
...@@ -1219,8 +1230,8 @@ Error Display::programCacheQuery(EGLint index, ...@@ -1219,8 +1230,8 @@ Error Display::programCacheQuery(EGLint index,
if (key) if (key)
{ {
ASSERT(*keysize == static_cast<EGLint>(gl::kProgramHashLength)); ASSERT(*keysize == static_cast<EGLint>(BlobCache::kKeyLength));
memcpy(key, programHash.data(), gl::kProgramHashLength); memcpy(key, programHash->data(), BlobCache::kKeyLength);
} }
if (binary) if (binary)
...@@ -1228,16 +1239,16 @@ Error Display::programCacheQuery(EGLint index, ...@@ -1228,16 +1239,16 @@ Error Display::programCacheQuery(EGLint index,
// Note: we check the size here instead of in the validation code, since we need to // Note: we check the size here instead of in the validation code, since we need to
// access the cache as atomically as possible. It's possible that the cache contents // access the cache as atomically as possible. It's possible that the cache contents
// could change between the validation size check and the retrieval. // could change between the validation size check and the retrieval.
if (programBinary->size() > static_cast<size_t>(*binarysize)) if (programBinary.size() > static_cast<size_t>(*binarysize))
{ {
return EglBadAccess() << "Program binary too large or changed during access."; return EglBadAccess() << "Program binary too large or changed during access.";
} }
memcpy(binary, programBinary->data(), programBinary->size()); memcpy(binary, programBinary.data(), programBinary.size());
} }
*binarysize = static_cast<EGLint>(programBinary->size()); *binarysize = static_cast<EGLint>(programBinary.size());
*keysize = static_cast<EGLint>(gl::kProgramHashLength); *keysize = static_cast<EGLint>(BlobCache::kKeyLength);
return NoError(); return NoError();
} }
...@@ -1247,10 +1258,10 @@ Error Display::programCachePopulate(const void *key, ...@@ -1247,10 +1258,10 @@ Error Display::programCachePopulate(const void *key,
const void *binary, const void *binary,
EGLint binarysize) EGLint binarysize)
{ {
ASSERT(keysize == static_cast<EGLint>(gl::kProgramHashLength)); ASSERT(keysize == static_cast<EGLint>(BlobCache::kKeyLength));
gl::ProgramHash programHash; BlobCache::Key programHash;
memcpy(programHash.data(), key, gl::kProgramHashLength); memcpy(programHash.data(), key, BlobCache::kKeyLength);
mMemoryProgramCache.putBinary(programHash, reinterpret_cast<const uint8_t *>(binary), mMemoryProgramCache.putBinary(programHash, reinterpret_cast<const uint8_t *>(binary),
static_cast<size_t>(binarysize)); static_cast<size_t>(binarysize));
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include <vector> #include <vector>
#include "libANGLE/AttributeMap.h" #include "libANGLE/AttributeMap.h"
#include "libANGLE/BlobCache.h"
#include "libANGLE/Caps.h" #include "libANGLE/Caps.h"
#include "libANGLE/Config.h" #include "libANGLE/Config.h"
#include "libANGLE/Debug.h" #include "libANGLE/Debug.h"
...@@ -134,6 +135,10 @@ class Display final : public LabeledObject, angle::NonCopyable ...@@ -134,6 +135,10 @@ class Display final : public LabeledObject, angle::NonCopyable
bool testDeviceLost(); bool testDeviceLost();
void notifyDeviceLost(); void notifyDeviceLost();
void setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get);
bool areBlobCacheFuncsSet() const { return mBlobCache.areBlobCacheFuncsSet(); }
BlobCache &getBlobCache() { return mBlobCache; }
Error waitClient(const gl::Context *context); Error waitClient(const gl::Context *context);
Error waitNative(const gl::Context *context, EGLint engine); Error waitNative(const gl::Context *context, EGLint engine);
...@@ -208,6 +213,7 @@ class Display final : public LabeledObject, angle::NonCopyable ...@@ -208,6 +213,7 @@ class Display final : public LabeledObject, angle::NonCopyable
angle::LoggingAnnotator mAnnotator; angle::LoggingAnnotator mAnnotator;
gl::TextureManager *mTextureManager; gl::TextureManager *mTextureManager;
BlobCache mBlobCache;
gl::MemoryProgramCache mMemoryProgramCache; gl::MemoryProgramCache mMemoryProgramCache;
size_t mGlobalTextureShareGroupUsers; size_t mGlobalTextureShareGroupUsers;
}; };
......
...@@ -26,14 +26,6 @@ namespace gl ...@@ -26,14 +26,6 @@ namespace gl
namespace namespace
{ {
enum CacheResult
{
kCacheMiss,
kCacheHitMemory,
kCacheHitDisk,
kCacheResultMax,
};
constexpr unsigned int kWarningLimit = 3; constexpr unsigned int kWarningLimit = 3;
void WriteShaderVar(BinaryOutputStream *stream, const sh::ShaderVariable &var) void WriteShaderVar(BinaryOutputStream *stream, const sh::ShaderVariable &var)
...@@ -198,8 +190,8 @@ HashStream &operator<<(HashStream &stream, const std::vector<std::string> &strin ...@@ -198,8 +190,8 @@ HashStream &operator<<(HashStream &stream, const std::vector<std::string> &strin
} // anonymous namespace } // anonymous namespace
MemoryProgramCache::MemoryProgramCache(size_t maxCacheSizeBytes) MemoryProgramCache::MemoryProgramCache(egl::BlobCache &blobCache)
: mProgramBinaryCache(maxCacheSizeBytes), mIssuedWarnings(0) : mBlobCache(blobCache), mIssuedWarnings(0)
{ {
} }
...@@ -624,7 +616,7 @@ void MemoryProgramCache::Serialize(const Context *context, ...@@ -624,7 +616,7 @@ void MemoryProgramCache::Serialize(const Context *context,
// static // static
void MemoryProgramCache::ComputeHash(const Context *context, void MemoryProgramCache::ComputeHash(const Context *context,
const Program *program, const Program *program,
ProgramHash *hashOut) egl::BlobCache::Key *hashOut)
{ {
// Compute the program hash. Start with the shader hashes and resource strings. // Compute the program hash. Start with the shader hashes and resource strings.
HashStream hashStream; HashStream hashStream;
...@@ -652,16 +644,16 @@ void MemoryProgramCache::ComputeHash(const Context *context, ...@@ -652,16 +644,16 @@ void MemoryProgramCache::ComputeHash(const Context *context,
LinkResult MemoryProgramCache::getProgram(const Context *context, LinkResult MemoryProgramCache::getProgram(const Context *context,
const Program *program, const Program *program,
ProgramState *state, ProgramState *state,
ProgramHash *hashOut) egl::BlobCache::Key *hashOut)
{ {
ComputeHash(context, program, hashOut); ComputeHash(context, program, hashOut);
const angle::MemoryBuffer *binaryProgram = nullptr; egl::BlobCache::Value binaryProgram;
LinkResult result(false); LinkResult result(false);
if (get(*hashOut, &binaryProgram)) if (get(context, *hashOut, &binaryProgram))
{ {
InfoLog infoLog; InfoLog infoLog;
ANGLE_TRY_RESULT(Deserialize(context, program, state, binaryProgram->data(), ANGLE_TRY_RESULT(Deserialize(context, program, state, binaryProgram.data(),
binaryProgram->size(), infoLog), binaryProgram.size(), infoLog),
result); result);
ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.ProgramCache.LoadBinarySuccess", result.getResult()); ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.ProgramCache.LoadBinarySuccess", result.getResult());
if (!result.getResult()) if (!result.getResult())
...@@ -683,129 +675,93 @@ LinkResult MemoryProgramCache::getProgram(const Context *context, ...@@ -683,129 +675,93 @@ LinkResult MemoryProgramCache::getProgram(const Context *context,
return result; return result;
} }
bool MemoryProgramCache::get(const ProgramHash &programHash, const angle::MemoryBuffer **programOut) bool MemoryProgramCache::get(const Context *context,
const egl::BlobCache::Key &programHash,
egl::BlobCache::Value *programOut)
{ {
const CacheEntry *entry = nullptr; return mBlobCache.get(context, programHash, programOut);
if (!mProgramBinaryCache.get(programHash, &entry))
{
ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheMiss,
kCacheResultMax);
return false;
}
if (entry->second == CacheSource::PutProgram)
{
ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheHitMemory,
kCacheResultMax);
}
else
{
ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.ProgramCache.CacheResult", kCacheHitDisk,
kCacheResultMax);
}
*programOut = &entry->first;
return true;
} }
bool MemoryProgramCache::getAt(size_t index, bool MemoryProgramCache::getAt(size_t index,
ProgramHash *hashOut, const egl::BlobCache::Key **hashOut,
const angle::MemoryBuffer **programOut) egl::BlobCache::Value *programOut)
{ {
const CacheEntry *entry = nullptr; return mBlobCache.getAt(index, hashOut, programOut);
if (!mProgramBinaryCache.getAt(index, hashOut, &entry))
{
return false;
}
*programOut = &entry->first;
return true;
} }
void MemoryProgramCache::remove(const ProgramHash &programHash) void MemoryProgramCache::remove(const egl::BlobCache::Key &programHash)
{ {
bool result = mProgramBinaryCache.eraseByKey(programHash); mBlobCache.remove(programHash);
ASSERT(result);
} }
void MemoryProgramCache::putProgram(const ProgramHash &programHash, void MemoryProgramCache::putProgram(const egl::BlobCache::Key &programHash,
const Context *context, const Context *context,
const Program *program) const Program *program)
{ {
CacheEntry newEntry; angle::MemoryBuffer serializedProgram;
Serialize(context, program, &newEntry.first); Serialize(context, program, &serializedProgram);
newEntry.second = CacheSource::PutProgram;
ANGLE_HISTOGRAM_COUNTS("GPU.ANGLE.ProgramCache.ProgramBinarySizeBytes", ANGLE_HISTOGRAM_COUNTS("GPU.ANGLE.ProgramCache.ProgramBinarySizeBytes",
static_cast<int>(newEntry.first.size())); static_cast<int>(serializedProgram.size()));
const CacheEntry *result = // TODO(syoussefi): to be removed. Compatibility for Chrome until it supports
mProgramBinaryCache.put(programHash, std::move(newEntry), newEntry.first.size()); // EGL_ANDROID_blob_cache. http://anglebug.com/2516
if (!result) auto *platform = ANGLEPlatformCurrent();
{ platform->cacheProgram(platform, programHash, serializedProgram.size(),
ERR() << "Failed to store binary program in memory cache, program is too large."; serializedProgram.data());
}
else mBlobCache.put(programHash, std::move(serializedProgram));
{
auto *platform = ANGLEPlatformCurrent();
platform->cacheProgram(platform, programHash, result->first.size(), result->first.data());
}
} }
void MemoryProgramCache::updateProgram(const Context *context, const Program *program) void MemoryProgramCache::updateProgram(const Context *context, const Program *program)
{ {
gl::ProgramHash programHash; egl::BlobCache::Key programHash;
ComputeHash(context, program, &programHash); ComputeHash(context, program, &programHash);
putProgram(programHash, context, program); putProgram(programHash, context, program);
} }
void MemoryProgramCache::putBinary(const ProgramHash &programHash, void MemoryProgramCache::putBinary(const egl::BlobCache::Key &programHash,
const uint8_t *binary, const uint8_t *binary,
size_t length) size_t length)
{ {
// Copy the binary. // Copy the binary.
CacheEntry newEntry; angle::MemoryBuffer newEntry;
newEntry.first.resize(length); newEntry.resize(length);
memcpy(newEntry.first.data(), binary, length); memcpy(newEntry.data(), binary, length);
newEntry.second = CacheSource::PutBinary;
// Store the binary. // Store the binary.
const CacheEntry *result = mProgramBinaryCache.put(programHash, std::move(newEntry), length); mBlobCache.populate(programHash, std::move(newEntry));
if (!result)
{
ERR() << "Failed to store binary program in memory cache, program is too large.";
}
} }
void MemoryProgramCache::clear() void MemoryProgramCache::clear()
{ {
mProgramBinaryCache.clear(); mBlobCache.clear();
mIssuedWarnings = 0; mIssuedWarnings = 0;
} }
void MemoryProgramCache::resize(size_t maxCacheSizeBytes) void MemoryProgramCache::resize(size_t maxCacheSizeBytes)
{ {
mProgramBinaryCache.resize(maxCacheSizeBytes); mBlobCache.resize(maxCacheSizeBytes);
} }
size_t MemoryProgramCache::entryCount() const size_t MemoryProgramCache::entryCount() const
{ {
return mProgramBinaryCache.entryCount(); return mBlobCache.entryCount();
} }
size_t MemoryProgramCache::trim(size_t limit) size_t MemoryProgramCache::trim(size_t limit)
{ {
return mProgramBinaryCache.shrinkToSize(limit); return mBlobCache.trim(limit);
} }
size_t MemoryProgramCache::size() const size_t MemoryProgramCache::size() const
{ {
return mProgramBinaryCache.size(); return mBlobCache.size();
} }
size_t MemoryProgramCache::maxSize() const size_t MemoryProgramCache::maxSize() const
{ {
return mProgramBinaryCache.maxSize(); return mBlobCache.maxSize();
} }
} // namespace gl } // namespace gl
// //
// Copyright 2017 The ANGLE Project Authors. All rights reserved. // Copyright 2017-2018 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// //
...@@ -13,34 +13,8 @@ ...@@ -13,34 +13,8 @@
#include <array> #include <array>
#include "common/MemoryBuffer.h" #include "common/MemoryBuffer.h"
#include "libANGLE/BlobCache.h"
#include "libANGLE/Error.h" #include "libANGLE/Error.h"
#include "libANGLE/SizedMRUCache.h"
namespace gl
{
// 160-bit SHA-1 hash key.
constexpr size_t kProgramHashLength = 20;
using ProgramHash = std::array<uint8_t, kProgramHashLength>;
} // namespace gl
namespace std
{
template <>
struct hash<gl::ProgramHash>
{
// Simple routine to hash four ints.
size_t operator()(const gl::ProgramHash &programHash) const
{
unsigned int hash = 0;
for (uint32_t num : programHash)
{
hash *= 37;
hash += num;
}
return hash;
}
};
} // namespace std
namespace gl namespace gl
{ {
...@@ -52,7 +26,7 @@ class ProgramState; ...@@ -52,7 +26,7 @@ class ProgramState;
class MemoryProgramCache final : angle::NonCopyable class MemoryProgramCache final : angle::NonCopyable
{ {
public: public:
MemoryProgramCache(size_t maxCacheSizeBytes); explicit MemoryProgramCache(egl::BlobCache &blobCache);
~MemoryProgramCache(); ~MemoryProgramCache();
// Writes a program's binary to the output memory buffer. // Writes a program's binary to the output memory buffer.
...@@ -68,32 +42,41 @@ class MemoryProgramCache final : angle::NonCopyable ...@@ -68,32 +42,41 @@ class MemoryProgramCache final : angle::NonCopyable
size_t length, size_t length,
InfoLog &infoLog); InfoLog &infoLog);
static void ComputeHash(const Context *context, const Program *program, ProgramHash *hashOut); static void ComputeHash(const Context *context,
const Program *program,
egl::BlobCache::Key *hashOut);
// Check if the cache contains a binary matching the specified program. // Check if the cache contains a binary matching the specified program.
bool get(const ProgramHash &programHash, const angle::MemoryBuffer **programOut); bool get(const Context *context,
const egl::BlobCache::Key &programHash,
egl::BlobCache::Value *programOut);
// For querying the contents of the cache. // For querying the contents of the cache.
bool getAt(size_t index, ProgramHash *hashOut, const angle::MemoryBuffer **programOut); bool getAt(size_t index,
const egl::BlobCache::Key **hashOut,
egl::BlobCache::Value *programOut);
// Evict a program from the binary cache. // Evict a program from the binary cache.
void remove(const ProgramHash &programHash); void remove(const egl::BlobCache::Key &programHash);
// Helper method that serializes a program. // Helper method that serializes a program.
void putProgram(const ProgramHash &programHash, const Context *context, const Program *program); void putProgram(const egl::BlobCache::Key &programHash,
const Context *context,
const Program *program);
// Same as putProgram but computes the hash. // Same as putProgram but computes the hash.
void updateProgram(const Context *context, const Program *program); void updateProgram(const Context *context, const Program *program);
// Store a binary directly. // Store a binary directly. TODO(syoussefi): deprecated. Will be removed once Chrome supports
void putBinary(const ProgramHash &programHash, const uint8_t *binary, size_t length); // EGL_ANDROID_blob_cache. http://anglebug.com/2516
void putBinary(const egl::BlobCache::Key &programHash, const uint8_t *binary, size_t length);
// Check the cache, and deserialize and load the program if found. Evict existing hash if load // Check the cache, and deserialize and load the program if found. Evict existing hash if load
// fails. // fails.
LinkResult getProgram(const Context *context, LinkResult getProgram(const Context *context,
const Program *program, const Program *program,
ProgramState *state, ProgramState *state,
ProgramHash *hashOut); egl::BlobCache::Key *hashOut);
// Empty the cache. // Empty the cache.
void clear(); void clear();
...@@ -114,14 +97,7 @@ class MemoryProgramCache final : angle::NonCopyable ...@@ -114,14 +97,7 @@ class MemoryProgramCache final : angle::NonCopyable
size_t maxSize() const; size_t maxSize() const;
private: private:
enum class CacheSource egl::BlobCache &mBlobCache;
{
PutProgram,
PutBinary,
};
using CacheEntry = std::pair<angle::MemoryBuffer, CacheSource>;
angle::SizedMRUCache<ProgramHash, CacheEntry> mProgramBinaryCache;
unsigned int mIssuedWarnings; unsigned int mIssuedWarnings;
}; };
......
...@@ -600,7 +600,7 @@ struct Program::LinkingState ...@@ -600,7 +600,7 @@ struct Program::LinkingState
{ {
const Context *context; const Context *context;
std::unique_ptr<ProgramLinkedResources> resources; std::unique_ptr<ProgramLinkedResources> resources;
ProgramHash programHash; egl::BlobCache::Key programHash;
std::unique_ptr<rx::LinkEvent> linkEvent; std::unique_ptr<rx::LinkEvent> linkEvent;
}; };
...@@ -1114,7 +1114,7 @@ Error Program::link(const gl::Context *context) ...@@ -1114,7 +1114,7 @@ Error Program::link(const gl::Context *context)
return NoError(); return NoError();
} }
ProgramHash programHash = {0}; egl::BlobCache::Key programHash = {0};
MemoryProgramCache *cache = context->getMemoryProgramCache(); MemoryProgramCache *cache = context->getMemoryProgramCache();
if (cache) if (cache)
......
...@@ -55,13 +55,13 @@ class SizedMRUCache final : angle::NonCopyable ...@@ -55,13 +55,13 @@ class SizedMRUCache final : angle::NonCopyable
return true; return true;
} }
bool getAt(size_t index, Key *keyOut, const Value **valueOut) bool getAt(size_t index, const Key **keyOut, const Value **valueOut)
{ {
if (index < mStore.size()) if (index < mStore.size())
{ {
auto it = mStore.begin(); auto it = mStore.begin();
std::advance(it, index); std::advance(it, index);
*keyOut = it->first; *keyOut = &it->first;
*valueOut = &it->second.value; *valueOut = &it->second.value;
return true; return true;
} }
...@@ -158,16 +158,22 @@ void TrimCache(size_t maxStates, size_t gcLimit, const char *name, T *cache) ...@@ -158,16 +158,22 @@ void TrimCache(size_t maxStates, size_t gcLimit, const char *name, T *cache)
} }
} }
// Computes a hash of struct "key". Any structs passed to this function must be multiples of // Computes a hash of "key". Any data passed to this function must be multiples of
// 4 bytes, since the PMurhHas32 method can only operate increments of 4-byte words. // 4 bytes, since the PMurHash32 method can only operate increments of 4-byte words.
static inline std::size_t ComputeGenericHash(const void *key, size_t keySize)
{
static constexpr unsigned int seed = 0xABCDEF98;
// We can't support "odd" alignments. ComputeGenericHash requires aligned types
ASSERT(keySize % 4 == 0);
return PMurHash32(seed, key, static_cast<int>(keySize));
}
template <typename T> template <typename T>
std::size_t ComputeGenericHash(const T &key) std::size_t ComputeGenericHash(const T &key)
{ {
static const unsigned int seed = 0xABCDEF98;
// We can't support "odd" alignments.
static_assert(sizeof(key) % 4 == 0, "ComputeGenericHash requires aligned types"); static_assert(sizeof(key) % 4 == 0, "ComputeGenericHash requires aligned types");
return PMurHash32(seed, &key, sizeof(T)); return ComputeGenericHash(&key, sizeof(key));
} }
} // namespace angle } // namespace angle
......
...@@ -5,9 +5,10 @@ ...@@ -5,9 +5,10 @@
// //
// SizedMRUCache_unittest.h: Unit tests for the sized MRU cached. // SizedMRUCache_unittest.h: Unit tests for the sized MRU cached.
#include "libANGLE/SizedMRUCache.h"
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "libANGLE/SizedMRUCache.h"
namespace angle namespace angle
{ {
......
...@@ -75,6 +75,8 @@ class DisplayImpl : public EGLImplFactory ...@@ -75,6 +75,8 @@ class DisplayImpl : public EGLImplFactory
virtual gl::Version getMaxSupportedESVersion() const = 0; virtual gl::Version getMaxSupportedESVersion() const = 0;
const egl::Caps &getCaps() const; const egl::Caps &getCaps() const;
virtual void setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get) {}
const egl::DisplayExtensions &getExtensions() const; const egl::DisplayExtensions &getExtensions() const;
protected: protected:
......
...@@ -139,4 +139,11 @@ void DisplayEGL::generateCaps(egl::Caps *outCaps) const ...@@ -139,4 +139,11 @@ void DisplayEGL::generateCaps(egl::Caps *outCaps) const
outCaps->textureNPOT = true; // Since we request GLES >= 2 outCaps->textureNPOT = true; // Since we request GLES >= 2
} }
void DisplayEGL::setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get)
{
ASSERT(mEGL->hasExtension("EGL_ANDROID_blob_cache"));
mEGL->setBlobCacheFuncsANDROID(set, get);
}
} // namespace rx } // namespace rx
...@@ -28,6 +28,8 @@ class DisplayEGL : public DisplayGL ...@@ -28,6 +28,8 @@ class DisplayEGL : public DisplayGL
std::string getVendorString() const override; std::string getVendorString() const override;
void setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get) override;
virtual void destroyNativeContext(EGLContext context) = 0; virtual void destroyNativeContext(EGLContext context) = 0;
protected: protected:
......
...@@ -66,7 +66,9 @@ struct FunctionsEGL::EGLDispatchTable ...@@ -66,7 +66,9 @@ struct FunctionsEGL::EGLDispatchTable
swapBuffersWithDamageKHRPtr(nullptr), swapBuffersWithDamageKHRPtr(nullptr),
presentationTimeANDROIDPtr(nullptr) presentationTimeANDROIDPtr(nullptr),
setBlobCacheFuncsANDROIDPtr(nullptr)
{ {
} }
...@@ -108,6 +110,9 @@ struct FunctionsEGL::EGLDispatchTable ...@@ -108,6 +110,9 @@ struct FunctionsEGL::EGLDispatchTable
// EGL_ANDROID_presentation_time // EGL_ANDROID_presentation_time
PFNEGLPRESENTATIONTIMEANDROIDPROC presentationTimeANDROIDPtr; PFNEGLPRESENTATIONTIMEANDROIDPROC presentationTimeANDROIDPtr;
// EGL_ANDROID_blob_cache
PFNEGLSETBLOBCACHEFUNCSANDROIDPROC setBlobCacheFuncsANDROIDPtr;
}; };
FunctionsEGL::FunctionsEGL() FunctionsEGL::FunctionsEGL()
...@@ -197,6 +202,11 @@ egl::Error FunctionsEGL::initialize(EGLNativeDisplayType nativeDisplay) ...@@ -197,6 +202,11 @@ egl::Error FunctionsEGL::initialize(EGLNativeDisplayType nativeDisplay)
ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->presentationTimeANDROIDPtr, eglPresentationTimeANDROID); ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->presentationTimeANDROIDPtr, eglPresentationTimeANDROID);
} }
if (hasExtension("EGL_ANDROID_blob_cache"))
{
ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->setBlobCacheFuncsANDROIDPtr, eglSetBlobCacheFuncsANDROID);
}
#undef ANGLE_GET_PROC_OR_ERROR #undef ANGLE_GET_PROC_OR_ERROR
return egl::NoError(); return egl::NoError();
...@@ -369,4 +379,10 @@ EGLBoolean FunctionsEGL::presentationTimeANDROID(EGLSurface surface, EGLnsecsAND ...@@ -369,4 +379,10 @@ EGLBoolean FunctionsEGL::presentationTimeANDROID(EGLSurface surface, EGLnsecsAND
{ {
return mFnPtrs->presentationTimeANDROIDPtr(mEGLDisplay, surface, time); return mFnPtrs->presentationTimeANDROIDPtr(mEGLDisplay, surface, time);
} }
void FunctionsEGL::setBlobCacheFuncsANDROID(EGLSetBlobFuncANDROID set,
EGLGetBlobFuncANDROID get) const
{
return mFnPtrs->setBlobCacheFuncsANDROIDPtr(mEGLDisplay, set, get);
}
} // namespace rx } // namespace rx
...@@ -79,6 +79,8 @@ class FunctionsEGL ...@@ -79,6 +79,8 @@ class FunctionsEGL
EGLBoolean presentationTimeANDROID(EGLSurface surface, EGLnsecsANDROID time) const; EGLBoolean presentationTimeANDROID(EGLSurface surface, EGLnsecsANDROID time) const;
void setBlobCacheFuncsANDROID(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get) const;
private: private:
// So as to isolate from angle we do not include angleutils.h and cannot // So as to isolate from angle we do not include angleutils.h and cannot
// use angle::NonCopyable so we replicated it here instead. // use angle::NonCopyable so we replicated it here instead.
......
...@@ -2715,6 +2715,26 @@ Error ValidatePresentationTimeANDROID(const Display *display, ...@@ -2715,6 +2715,26 @@ Error ValidatePresentationTimeANDROID(const Display *display,
return NoError(); return NoError();
} }
Error ValidateSetBlobCacheANDROID(const Display *display,
EGLSetBlobFuncANDROID set,
EGLGetBlobFuncANDROID get)
{
ANGLE_TRY(ValidateDisplay(display));
if (display->areBlobCacheFuncsSet())
{
return EglBadParameter()
<< "Blob cache functions can only be set once in the lifetime of a Display";
}
if (set == nullptr || get == nullptr)
{
return EglBadParameter() << "Blob cache callbacks cannot be null.";
}
return NoError();
}
Error ValidateGetConfigAttrib(const Display *display, const Config *config, EGLint attribute) Error ValidateGetConfigAttrib(const Display *display, const Config *config, EGLint attribute)
{ {
ANGLE_TRY(ValidateConfig(display, config)); ANGLE_TRY(ValidateConfig(display, config));
...@@ -2842,7 +2862,7 @@ Error ValidateProgramCacheQueryANGLE(const Display *display, ...@@ -2842,7 +2862,7 @@ Error ValidateProgramCacheQueryANGLE(const Display *display,
return EglBadParameter() << "keysize and binarysize must always be valid pointers."; return EglBadParameter() << "keysize and binarysize must always be valid pointers.";
} }
if (binary && *keysize != static_cast<EGLint>(gl::kProgramHashLength)) if (binary && *keysize != static_cast<EGLint>(egl::BlobCache::kKeyLength))
{ {
return EglBadParameter() << "Invalid program key size."; return EglBadParameter() << "Invalid program key size.";
} }
...@@ -2868,7 +2888,7 @@ Error ValidateProgramCachePopulateANGLE(const Display *display, ...@@ -2868,7 +2888,7 @@ Error ValidateProgramCachePopulateANGLE(const Display *display,
return EglBadAccess() << "Extension not supported"; return EglBadAccess() << "Extension not supported";
} }
if (keysize != static_cast<EGLint>(gl::kProgramHashLength)) if (keysize != static_cast<EGLint>(egl::BlobCache::kKeyLength))
{ {
return EglBadParameter() << "Invalid program key size."; return EglBadParameter() << "Invalid program key size.";
} }
......
...@@ -174,6 +174,10 @@ Error ValidatePresentationTimeANDROID(const Display *display, ...@@ -174,6 +174,10 @@ Error ValidatePresentationTimeANDROID(const Display *display,
const Surface *surface, const Surface *surface,
EGLnsecsANDROID time); EGLnsecsANDROID time);
Error ValidateSetBlobCacheANDROID(const Display *display,
EGLSetBlobFuncANDROID set,
EGLGetBlobFuncANDROID get);
Error ValidateGetConfigAttrib(const Display *display, const Config *config, EGLint attribute); Error ValidateGetConfigAttrib(const Display *display, const Config *config, EGLint attribute);
Error ValidateChooseConfig(const Display *display, Error ValidateChooseConfig(const Display *display,
const AttributeMap &attribs, const AttributeMap &attribs,
......
...@@ -511,4 +511,11 @@ EGLint EGLAPIENTRY eglLabelObjectKHR(EGLDisplay dpy, ...@@ -511,4 +511,11 @@ EGLint EGLAPIENTRY eglLabelObjectKHR(EGLDisplay dpy,
return egl::LabelObjectKHR(dpy, objectType, object, label); return egl::LabelObjectKHR(dpy, objectType, object, label);
} }
void EGLAPIENTRY eglSetBlobCacheFuncsANDROID(EGLDisplay dpy,
EGLSetBlobFuncANDROID set,
EGLGetBlobFuncANDROID get)
{
return egl::SetBlobCacheFuncsANDROID(dpy, set, get);
}
} // extern "C" } // extern "C"
...@@ -69,6 +69,7 @@ EXPORTS ...@@ -69,6 +69,7 @@ EXPORTS
eglDebugMessageControlKHR @75 eglDebugMessageControlKHR @75
eglQueryDebugKHR @76 eglQueryDebugKHR @76
eglLabelObjectKHR @77 eglLabelObjectKHR @77
eglSetBlobCacheFuncsANDROID @78
; 1.5 entry points ; 1.5 entry points
eglCreateSync @38 eglCreateSync @38
......
...@@ -116,6 +116,8 @@ libangle_sources = [ ...@@ -116,6 +116,8 @@ libangle_sources = [
"src/libANGLE/AttributeMap.cpp", "src/libANGLE/AttributeMap.cpp",
"src/libANGLE/AttributeMap.h", "src/libANGLE/AttributeMap.h",
"src/libANGLE/BinaryStream.h", "src/libANGLE/BinaryStream.h",
"src/libANGLE/BlobCache.cpp",
"src/libANGLE/BlobCache.h",
"src/libANGLE/Buffer.cpp", "src/libANGLE/Buffer.cpp",
"src/libANGLE/Buffer.h", "src/libANGLE/Buffer.h",
"src/libANGLE/Caps.cpp", "src/libANGLE/Caps.cpp",
......
...@@ -949,6 +949,25 @@ EGLBoolean EGLAPIENTRY PresentationTimeANDROID(EGLDisplay dpy, ...@@ -949,6 +949,25 @@ EGLBoolean EGLAPIENTRY PresentationTimeANDROID(EGLDisplay dpy,
return EGL_TRUE; return EGL_TRUE;
} }
ANGLE_EXPORT void EGLAPIENTRY SetBlobCacheFuncsANDROID(EGLDisplay dpy,
EGLSetBlobFuncANDROID set,
EGLGetBlobFuncANDROID get)
{
EVENT(
"(EGLDisplay dpy = 0x%0.8p, EGLSetBlobFuncANDROID set = 0x%0.8p, EGLGetBlobFuncANDROID get "
"= 0x%0.8p)",
dpy, set, get);
Thread *thread = GetCurrentThread();
Display *display = static_cast<Display *>(dpy);
ANGLE_EGL_TRY(thread, ValidateSetBlobCacheANDROID(display, set, get),
"eglSetBlobCacheFuncsANDROID", GetDisplayIfValid(display));
thread->setSuccess();
display->setBlobCacheFuncs(set, get);
}
EGLint EGLAPIENTRY ProgramCacheGetAttribANGLE(EGLDisplay dpy, EGLenum attrib) EGLint EGLAPIENTRY ProgramCacheGetAttribANGLE(EGLDisplay dpy, EGLenum attrib)
{ {
ANGLE_SCOPED_GLOBAL_LOCK(); ANGLE_SCOPED_GLOBAL_LOCK();
......
...@@ -120,6 +120,11 @@ ANGLE_EXPORT EGLBoolean EGLAPIENTRY PresentationTimeANDROID(EGLDisplay dpy, ...@@ -120,6 +120,11 @@ ANGLE_EXPORT EGLBoolean EGLAPIENTRY PresentationTimeANDROID(EGLDisplay dpy,
EGLSurface surface, EGLSurface surface,
EGLnsecsANDROID time); EGLnsecsANDROID time);
// EGL_ANDRIOD_blob_cache
ANGLE_EXPORT void EGLAPIENTRY SetBlobCacheFuncsANDROID(EGLDisplay dpy,
EGLSetBlobFuncANDROID set,
EGLGetBlobFuncANDROID get);
// EGL_ANGLE_program_cache_control // EGL_ANGLE_program_cache_control
ANGLE_EXPORT EGLint EGLAPIENTRY ProgramCacheGetAttribANGLE(EGLDisplay dpy, EGLenum attrib); ANGLE_EXPORT EGLint EGLAPIENTRY ProgramCacheGetAttribANGLE(EGLDisplay dpy, EGLenum attrib);
ANGLE_EXPORT void EGLAPIENTRY ProgramCacheQueryANGLE(EGLDisplay dpy, ANGLE_EXPORT void EGLAPIENTRY ProgramCacheQueryANGLE(EGLDisplay dpy,
......
...@@ -89,6 +89,7 @@ ProcEntry g_procTable[] = { ...@@ -89,6 +89,7 @@ ProcEntry g_procTable[] = {
{"eglReleaseDeviceANGLE", P(egl::ReleaseDeviceANGLE)}, {"eglReleaseDeviceANGLE", P(egl::ReleaseDeviceANGLE)},
{"eglReleaseTexImage", P(egl::ReleaseTexImage)}, {"eglReleaseTexImage", P(egl::ReleaseTexImage)},
{"eglReleaseThread", P(egl::ReleaseThread)}, {"eglReleaseThread", P(egl::ReleaseThread)},
{"eglSetBlobCacheFuncsANDROID", P(egl::SetBlobCacheFuncsANDROID)},
{"eglStreamAttribKHR", P(egl::StreamAttribKHR)}, {"eglStreamAttribKHR", P(egl::StreamAttribKHR)},
{"eglStreamConsumerAcquireKHR", P(egl::StreamConsumerAcquireKHR)}, {"eglStreamConsumerAcquireKHR", P(egl::StreamConsumerAcquireKHR)},
{"eglStreamConsumerGLTextureExternalAttribsNV", {"eglStreamConsumerGLTextureExternalAttribsNV",
...@@ -1241,5 +1242,5 @@ ProcEntry g_procTable[] = { ...@@ -1241,5 +1242,5 @@ ProcEntry g_procTable[] = {
{"glWeightPointerOES", P(gl::WeightPointerOES)}, {"glWeightPointerOES", P(gl::WeightPointerOES)},
{"glWeightPointerOESContextANGLE", P(gl::WeightPointerOESContextANGLE)}}; {"glWeightPointerOESContextANGLE", P(gl::WeightPointerOESContextANGLE)}};
size_t g_numProcs = 1173; size_t g_numProcs = 1174;
} // namespace egl } // namespace egl
...@@ -840,6 +840,10 @@ ...@@ -840,6 +840,10 @@
"eglPresentationTimeANDROID" "eglPresentationTimeANDROID"
], ],
"EGL_ANDROID_blob_cache": [
"eglSetBlobCacheFuncsANDROID"
],
"EGL_ANGLE_program_cache_control": [ "EGL_ANGLE_program_cache_control": [
"eglProgramCacheGetAttribANGLE", "eglProgramCacheGetAttribANGLE",
"eglProgramCacheQueryANGLE", "eglProgramCacheQueryANGLE",
......
...@@ -117,6 +117,7 @@ angle_end2end_tests_sources = [ ...@@ -117,6 +117,7 @@ angle_end2end_tests_sources = [
"gl_tests/WebGLCompatibilityTest.cpp", "gl_tests/WebGLCompatibilityTest.cpp",
"gl_tests/WebGLFramebufferTest.cpp", "gl_tests/WebGLFramebufferTest.cpp",
"gl_tests/WebGLReadOutsideFramebufferTest.cpp", "gl_tests/WebGLReadOutsideFramebufferTest.cpp",
"egl_tests/EGLBlobCacheTest.cpp",
"egl_tests/EGLContextCompatibilityTest.cpp", "egl_tests/EGLContextCompatibilityTest.cpp",
"egl_tests/EGLContextSharingTest.cpp", "egl_tests/EGLContextSharingTest.cpp",
"egl_tests/EGLDebugTest.cpp", "egl_tests/EGLDebugTest.cpp",
......
...@@ -15,6 +15,7 @@ angle_unittests_sources = [ ...@@ -15,6 +15,7 @@ angle_unittests_sources = [
"../common/vector_utils_unittest.cpp", "../common/vector_utils_unittest.cpp",
"../gpu_info_util/SystemInfo_unittest.cpp", "../gpu_info_util/SystemInfo_unittest.cpp",
"../libANGLE/BinaryStream_unittest.cpp", "../libANGLE/BinaryStream_unittest.cpp",
"../libANGLE/BlobCache_unittest.cpp",
"../libANGLE/Config_unittest.cpp", "../libANGLE/Config_unittest.cpp",
"../libANGLE/Fence_unittest.cpp", "../libANGLE/Fence_unittest.cpp",
"../libANGLE/HandleAllocator_unittest.cpp", "../libANGLE/HandleAllocator_unittest.cpp",
......
//
// 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.
//
// EGLBlobCacheTest:
// Unit tests for the EGL_ANDROID_blob_cache extension.
#include <map>
#include <vector>
#include "common/angleutils.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "libANGLE/validationEGL.h"
using namespace angle;
constexpr char kEGLExtName[] = "EGL_ANDROID_blob_cache";
enum class CacheOpResult
{
SET_SUCCESS,
GET_NOT_FOUND,
GET_MEMORY_TOO_SMALL,
GET_SUCCESS,
VALUE_NOT_SET,
};
namespace
{
std::map<std::vector<uint8_t>, std::vector<uint8_t>> gApplicationCache;
CacheOpResult gLastCacheOpResult = CacheOpResult::VALUE_NOT_SET;
void SetBlob(const void *key, EGLsizeiANDROID keySize, const void *value, EGLsizeiANDROID valueSize)
{
std::vector<uint8_t> keyVec(keySize);
memcpy(keyVec.data(), key, keySize);
std::vector<uint8_t> valueVec(valueSize);
memcpy(valueVec.data(), value, valueSize);
gApplicationCache[keyVec] = valueVec;
gLastCacheOpResult = CacheOpResult::SET_SUCCESS;
}
EGLsizeiANDROID GetBlob(const void *key,
EGLsizeiANDROID keySize,
void *value,
EGLsizeiANDROID valueSize)
{
std::vector<uint8_t> keyVec(keySize);
memcpy(keyVec.data(), key, keySize);
auto entry = gApplicationCache.find(keyVec);
if (entry == gApplicationCache.end())
{
gLastCacheOpResult = CacheOpResult::GET_NOT_FOUND;
return 0;
}
if (entry->second.size() <= static_cast<size_t>(valueSize))
{
memcpy(value, entry->second.data(), entry->second.size());
gLastCacheOpResult = CacheOpResult::GET_SUCCESS;
}
else
{
gLastCacheOpResult = CacheOpResult::GET_MEMORY_TOO_SMALL;
}
return entry->second.size();
}
} // anonymous namespace
class EGLBlobCacheTest : public ANGLETest
{
protected:
EGLBlobCacheTest() : mHasProgramCache(false) { setDeferContextInit(true); }
void SetUp() override
{
ANGLETest::SetUp();
// Enable the program cache so that angle would do caching, which eventually lands in the
// BlobCache
EGLDisplay display = getEGLWindow()->getDisplay();
if (eglDisplayExtensionEnabled(display, "EGL_ANGLE_program_cache_control"))
{
mHasProgramCache = true;
setContextProgramCacheEnabled(true);
eglProgramCacheResizeANGLE(display, 0x10000, EGL_PROGRAM_CACHE_RESIZE_ANGLE);
}
getEGLWindow()->initializeContext();
}
void TearDown() override { ANGLETest::TearDown(); }
bool extensionAvailable()
{
EGLDisplay display = getEGLWindow()->getDisplay();
return eglDisplayExtensionEnabled(display, kEGLExtName);
}
bool programBinaryAvailable()
{
return (getClientMajorVersion() >= 3 || extensionEnabled("GL_OES_get_program_binary"));
}
bool mHasProgramCache;
};
// Makes sure the extension exists and works
TEST_P(EGLBlobCacheTest, Functional)
{
EGLDisplay display = getEGLWindow()->getDisplay();
EXPECT_EQ(true, extensionAvailable());
eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
ASSERT_EGL_SUCCESS();
constexpr char kVertexShaderSrc[] = R"(attribute vec4 aTest;
attribute vec2 aPosition;
varying vec4 vTest;
void main()
{
vTest = aTest;
gl_Position = vec4(aPosition, 0.0, 1.0);
gl_PointSize = 1.0;
})";
constexpr char kFragmentShaderSrc[] = R"(precision mediump float;
varying vec4 vTest;
void main()
{
gl_FragColor = vTest;
})";
constexpr char kVertexShaderSrc2[] = R"(attribute vec4 aTest;
attribute vec2 aPosition;
varying vec4 vTest;
void main()
{
vTest = aTest;
gl_Position = vec4(aPosition, 1.0, 1.0);
gl_PointSize = 1.0;
})";
constexpr char kFragmentShaderSrc2[] = R"(precision mediump float;
varying vec4 vTest;
void main()
{
gl_FragColor = vTest - vec4(0.0, 1.0, 0.0, 0.0);
})";
// Compile a shader so it puts something in the cache
if (mHasProgramCache && programBinaryAvailable())
{
GLuint program = CompileProgram(kVertexShaderSrc, kFragmentShaderSrc);
ASSERT_NE(0u, program);
EXPECT_EQ(CacheOpResult::SET_SUCCESS, gLastCacheOpResult);
gLastCacheOpResult = CacheOpResult::VALUE_NOT_SET;
// Compile the same shader again, so it would try to retrieve it from the cache
program = CompileProgram(kVertexShaderSrc, kFragmentShaderSrc);
ASSERT_NE(0u, program);
EXPECT_EQ(CacheOpResult::GET_SUCCESS, gLastCacheOpResult);
gLastCacheOpResult = CacheOpResult::VALUE_NOT_SET;
// Compile another shader, which should create a new entry
program = CompileProgram(kVertexShaderSrc2, kFragmentShaderSrc2);
ASSERT_NE(0u, program);
EXPECT_EQ(CacheOpResult::SET_SUCCESS, gLastCacheOpResult);
gLastCacheOpResult = CacheOpResult::VALUE_NOT_SET;
// Compile the first shader again, which should still reside in the cache
program = CompileProgram(kVertexShaderSrc, kFragmentShaderSrc);
ASSERT_NE(0u, program);
EXPECT_EQ(CacheOpResult::GET_SUCCESS, gLastCacheOpResult);
gLastCacheOpResult = CacheOpResult::VALUE_NOT_SET;
}
}
// Tests error conditions of the APIs.
TEST_P(EGLBlobCacheTest, NegativeAPI)
{
EXPECT_EQ(true, extensionAvailable());
// Test bad display
eglSetBlobCacheFuncsANDROID(EGL_NO_DISPLAY, nullptr, nullptr);
EXPECT_EGL_ERROR(EGL_BAD_DISPLAY);
eglSetBlobCacheFuncsANDROID(EGL_NO_DISPLAY, SetBlob, GetBlob);
EXPECT_EGL_ERROR(EGL_BAD_DISPLAY);
EGLDisplay display = getEGLWindow()->getDisplay();
// Test bad arguments
eglSetBlobCacheFuncsANDROID(display, nullptr, nullptr);
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
eglSetBlobCacheFuncsANDROID(display, SetBlob, nullptr);
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
eglSetBlobCacheFuncsANDROID(display, nullptr, GetBlob);
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
// Set the arguments once and test setting them again (which should fail)
eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
ASSERT_EGL_SUCCESS();
eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
// Try again with bad parameters
eglSetBlobCacheFuncsANDROID(EGL_NO_DISPLAY, nullptr, nullptr);
EXPECT_EGL_ERROR(EGL_BAD_DISPLAY);
eglSetBlobCacheFuncsANDROID(display, nullptr, nullptr);
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
eglSetBlobCacheFuncsANDROID(display, SetBlob, nullptr);
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
eglSetBlobCacheFuncsANDROID(display, nullptr, GetBlob);
EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
}
ANGLE_INSTANTIATE_TEST(EGLBlobCacheTest, ES2_D3D9(), ES2_D3D11(), ES2_OPENGL());
...@@ -36,7 +36,7 @@ class EGLProgramCacheControlTest : public ANGLETest ...@@ -36,7 +36,7 @@ class EGLProgramCacheControlTest : public ANGLETest
{ {
mPlatformMethods.cacheProgram = &TestCacheProgram; mPlatformMethods.cacheProgram = &TestCacheProgram;
ANGLETestBase::ANGLETestSetUp(); ANGLETest::SetUp();
if (extensionAvailable()) if (extensionAvailable())
{ {
...@@ -48,7 +48,7 @@ class EGLProgramCacheControlTest : public ANGLETest ...@@ -48,7 +48,7 @@ class EGLProgramCacheControlTest : public ANGLETest
getEGLWindow()->initializeContext(); getEGLWindow()->initializeContext();
} }
void TearDown() override { ANGLETestBase::ANGLETestTearDown(); } void TearDown() override { ANGLETest::TearDown(); }
bool extensionAvailable() bool extensionAvailable()
{ {
......
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