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
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);
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()
robustResourceInitialization(false),
iosurfaceClientBuffer(false),
createContextExtensionsEnabled(false),
presentationTime(false)
presentationTime(false),
blobCache(false)
{
}
......@@ -1366,6 +1367,7 @@ std::vector<std::string> DisplayExtensions::getStrings() const
InsertExtensionString("EGL_ANGLE_iosurface_client_buffer", iosurfaceClientBuffer, &extensionStrings);
InsertExtensionString("EGL_ANGLE_create_context_extensions_enabled", createContextExtensionsEnabled, &extensionStrings);
InsertExtensionString("EGL_ANDROID_presentation_time", presentationTime, &extensionStrings);
InsertExtensionString("EGL_ANDROID_blob_cache", blobCache, &extensionStrings);
// TODO(jmadill): Enable this when complete.
//InsertExtensionString("KHR_create_context_no_error", createContextNoError, &extensionStrings);
// clang-format on
......
......@@ -778,6 +778,9 @@ struct DisplayExtensions
// EGL_ANDROID_presentation_time
bool presentationTime;
// EGL_ANDROID_blob_cache
bool blobCache;
};
struct DeviceExtensions
......
......@@ -386,7 +386,8 @@ Display::Display(EGLenum platform, EGLNativeDisplayType displayId, Device *eglDe
mDevice(eglDevice),
mPlatform(platform),
mTextureManager(nullptr),
mMemoryProgramCache(gl::kDefaultMaxProgramCacheMemoryBytes),
mBlobCache(gl::kDefaultMaxProgramCacheMemoryBytes),
mMemoryProgramCache(mBlobCache),
mGlobalTextureShareGroupUsers(0)
{
}
......@@ -542,6 +543,7 @@ Error Display::terminate(const Thread *thread)
}
mMemoryProgramCache.clear();
mBlobCache.setBlobCacheFuncs(nullptr, nullptr);
while (!mContextSet.empty())
{
......@@ -947,6 +949,12 @@ void Display::notifyDeviceLost()
mDeviceLost = true;
}
void Display::setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get)
{
mBlobCache.setBlobCacheFuncs(set, get);
mImplementation->setBlobCacheFuncs(set, get);
}
Error Display::waitClient(const gl::Context *context)
{
return mImplementation->waitClient(context);
......@@ -1092,6 +1100,9 @@ void Display::initDisplayExtensions()
// Request extension is implemented in the ANGLE frontend
mDisplayExtensions.createContextExtensionsEnabled = true;
// Blob cache extension is provided by the ANGLE frontend
mDisplayExtensions.blobCache = true;
mDisplayExtensionString = GenerateExtensionsString(mDisplayExtensions);
}
......@@ -1186,7 +1197,7 @@ EGLint Display::programCacheGetAttrib(EGLenum attrib) const
switch (attrib)
{
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:
return static_cast<EGLint>(mMemoryProgramCache.entryCount());
......@@ -1205,8 +1216,8 @@ Error Display::programCacheQuery(EGLint index,
{
ASSERT(index >= 0 && index < static_cast<EGLint>(mMemoryProgramCache.entryCount()));
const angle::MemoryBuffer *programBinary = nullptr;
gl::ProgramHash programHash;
const BlobCache::Key *programHash = nullptr;
BlobCache::Value programBinary;
// TODO(jmadill): Make this thread-safe.
bool result =
mMemoryProgramCache.getAt(static_cast<size_t>(index), &programHash, &programBinary);
......@@ -1219,8 +1230,8 @@ Error Display::programCacheQuery(EGLint index,
if (key)
{
ASSERT(*keysize == static_cast<EGLint>(gl::kProgramHashLength));
memcpy(key, programHash.data(), gl::kProgramHashLength);
ASSERT(*keysize == static_cast<EGLint>(BlobCache::kKeyLength));
memcpy(key, programHash->data(), BlobCache::kKeyLength);
}
if (binary)
......@@ -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
// access the cache as atomically as possible. It's possible that the cache contents
// 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.";
}
memcpy(binary, programBinary->data(), programBinary->size());
memcpy(binary, programBinary.data(), programBinary.size());
}
*binarysize = static_cast<EGLint>(programBinary->size());
*keysize = static_cast<EGLint>(gl::kProgramHashLength);
*binarysize = static_cast<EGLint>(programBinary.size());
*keysize = static_cast<EGLint>(BlobCache::kKeyLength);
return NoError();
}
......@@ -1247,10 +1258,10 @@ Error Display::programCachePopulate(const void *key,
const void *binary,
EGLint binarysize)
{
ASSERT(keysize == static_cast<EGLint>(gl::kProgramHashLength));
ASSERT(keysize == static_cast<EGLint>(BlobCache::kKeyLength));
gl::ProgramHash programHash;
memcpy(programHash.data(), key, gl::kProgramHashLength);
BlobCache::Key programHash;
memcpy(programHash.data(), key, BlobCache::kKeyLength);
mMemoryProgramCache.putBinary(programHash, reinterpret_cast<const uint8_t *>(binary),
static_cast<size_t>(binarysize));
......
......@@ -15,6 +15,7 @@
#include <vector>
#include "libANGLE/AttributeMap.h"
#include "libANGLE/BlobCache.h"
#include "libANGLE/Caps.h"
#include "libANGLE/Config.h"
#include "libANGLE/Debug.h"
......@@ -134,6 +135,10 @@ class Display final : public LabeledObject, angle::NonCopyable
bool testDeviceLost();
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 waitNative(const gl::Context *context, EGLint engine);
......@@ -208,6 +213,7 @@ class Display final : public LabeledObject, angle::NonCopyable
angle::LoggingAnnotator mAnnotator;
gl::TextureManager *mTextureManager;
BlobCache mBlobCache;
gl::MemoryProgramCache mMemoryProgramCache;
size_t mGlobalTextureShareGroupUsers;
};
......
......@@ -26,14 +26,6 @@ namespace gl
namespace
{
enum CacheResult
{
kCacheMiss,
kCacheHitMemory,
kCacheHitDisk,
kCacheResultMax,
};
constexpr unsigned int kWarningLimit = 3;
void WriteShaderVar(BinaryOutputStream *stream, const sh::ShaderVariable &var)
......@@ -198,8 +190,8 @@ HashStream &operator<<(HashStream &stream, const std::vector<std::string> &strin
} // anonymous namespace
MemoryProgramCache::MemoryProgramCache(size_t maxCacheSizeBytes)
: mProgramBinaryCache(maxCacheSizeBytes), mIssuedWarnings(0)
MemoryProgramCache::MemoryProgramCache(egl::BlobCache &blobCache)
: mBlobCache(blobCache), mIssuedWarnings(0)
{
}
......@@ -624,7 +616,7 @@ void MemoryProgramCache::Serialize(const Context *context,
// static
void MemoryProgramCache::ComputeHash(const Context *context,
const Program *program,
ProgramHash *hashOut)
egl::BlobCache::Key *hashOut)
{
// Compute the program hash. Start with the shader hashes and resource strings.
HashStream hashStream;
......@@ -652,16 +644,16 @@ void MemoryProgramCache::ComputeHash(const Context *context,
LinkResult MemoryProgramCache::getProgram(const Context *context,
const Program *program,
ProgramState *state,
ProgramHash *hashOut)
egl::BlobCache::Key *hashOut)
{
ComputeHash(context, program, hashOut);
const angle::MemoryBuffer *binaryProgram = nullptr;
egl::BlobCache::Value binaryProgram;
LinkResult result(false);
if (get(*hashOut, &binaryProgram))
if (get(context, *hashOut, &binaryProgram))
{
InfoLog infoLog;
ANGLE_TRY_RESULT(Deserialize(context, program, state, binaryProgram->data(),
binaryProgram->size(), infoLog),
ANGLE_TRY_RESULT(Deserialize(context, program, state, binaryProgram.data(),
binaryProgram.size(), infoLog),
result);
ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.ProgramCache.LoadBinarySuccess", result.getResult());
if (!result.getResult())
......@@ -683,129 +675,93 @@ LinkResult MemoryProgramCache::getProgram(const Context *context,
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;
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;
return mBlobCache.get(context, programHash, programOut);
}
bool MemoryProgramCache::getAt(size_t index,
ProgramHash *hashOut,
const angle::MemoryBuffer **programOut)
const egl::BlobCache::Key **hashOut,
egl::BlobCache::Value *programOut)
{
const CacheEntry *entry = nullptr;
if (!mProgramBinaryCache.getAt(index, hashOut, &entry))
{
return false;
}
*programOut = &entry->first;
return true;
return mBlobCache.getAt(index, hashOut, programOut);
}
void MemoryProgramCache::remove(const ProgramHash &programHash)
void MemoryProgramCache::remove(const egl::BlobCache::Key &programHash)
{
bool result = mProgramBinaryCache.eraseByKey(programHash);
ASSERT(result);
mBlobCache.remove(programHash);
}
void MemoryProgramCache::putProgram(const ProgramHash &programHash,
void MemoryProgramCache::putProgram(const egl::BlobCache::Key &programHash,
const Context *context,
const Program *program)
{
CacheEntry newEntry;
Serialize(context, program, &newEntry.first);
newEntry.second = CacheSource::PutProgram;
angle::MemoryBuffer serializedProgram;
Serialize(context, program, &serializedProgram);
ANGLE_HISTOGRAM_COUNTS("GPU.ANGLE.ProgramCache.ProgramBinarySizeBytes",
static_cast<int>(newEntry.first.size()));
static_cast<int>(serializedProgram.size()));
const CacheEntry *result =
mProgramBinaryCache.put(programHash, std::move(newEntry), newEntry.first.size());
if (!result)
{
ERR() << "Failed to store binary program in memory cache, program is too large.";
}
else
{
auto *platform = ANGLEPlatformCurrent();
platform->cacheProgram(platform, programHash, result->first.size(), result->first.data());
}
// TODO(syoussefi): to be removed. Compatibility for Chrome until it supports
// EGL_ANDROID_blob_cache. http://anglebug.com/2516
auto *platform = ANGLEPlatformCurrent();
platform->cacheProgram(platform, programHash, serializedProgram.size(),
serializedProgram.data());
mBlobCache.put(programHash, std::move(serializedProgram));
}
void MemoryProgramCache::updateProgram(const Context *context, const Program *program)
{
gl::ProgramHash programHash;
egl::BlobCache::Key programHash;
ComputeHash(context, program, &programHash);
putProgram(programHash, context, program);
}
void MemoryProgramCache::putBinary(const ProgramHash &programHash,
void MemoryProgramCache::putBinary(const egl::BlobCache::Key &programHash,
const uint8_t *binary,
size_t length)
{
// Copy the binary.
CacheEntry newEntry;
newEntry.first.resize(length);
memcpy(newEntry.first.data(), binary, length);
newEntry.second = CacheSource::PutBinary;
angle::MemoryBuffer newEntry;
newEntry.resize(length);
memcpy(newEntry.data(), binary, length);
// Store the binary.
const CacheEntry *result = mProgramBinaryCache.put(programHash, std::move(newEntry), length);
if (!result)
{
ERR() << "Failed to store binary program in memory cache, program is too large.";
}
mBlobCache.populate(programHash, std::move(newEntry));
}
void MemoryProgramCache::clear()
{
mProgramBinaryCache.clear();
mBlobCache.clear();
mIssuedWarnings = 0;
}
void MemoryProgramCache::resize(size_t maxCacheSizeBytes)
{
mProgramBinaryCache.resize(maxCacheSizeBytes);
mBlobCache.resize(maxCacheSizeBytes);
}
size_t MemoryProgramCache::entryCount() const
{
return mProgramBinaryCache.entryCount();
return mBlobCache.entryCount();
}
size_t MemoryProgramCache::trim(size_t limit)
{
return mProgramBinaryCache.shrinkToSize(limit);
return mBlobCache.trim(limit);
}
size_t MemoryProgramCache::size() const
{
return mProgramBinaryCache.size();
return mBlobCache.size();
}
size_t MemoryProgramCache::maxSize() const
{
return mProgramBinaryCache.maxSize();
return mBlobCache.maxSize();
}
} // 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
// found in the LICENSE file.
//
......@@ -13,34 +13,8 @@
#include <array>
#include "common/MemoryBuffer.h"
#include "libANGLE/BlobCache.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
{
......@@ -52,7 +26,7 @@ class ProgramState;
class MemoryProgramCache final : angle::NonCopyable
{
public:
MemoryProgramCache(size_t maxCacheSizeBytes);
explicit MemoryProgramCache(egl::BlobCache &blobCache);
~MemoryProgramCache();
// Writes a program's binary to the output memory buffer.
......@@ -68,32 +42,41 @@ class MemoryProgramCache final : angle::NonCopyable
size_t length,
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.
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.
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.
void remove(const ProgramHash &programHash);
void remove(const egl::BlobCache::Key &programHash);
// 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.
void updateProgram(const Context *context, const Program *program);
// Store a binary directly.
void putBinary(const ProgramHash &programHash, const uint8_t *binary, size_t length);
// Store a binary directly. TODO(syoussefi): deprecated. Will be removed once Chrome supports
// 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
// fails.
LinkResult getProgram(const Context *context,
const Program *program,
ProgramState *state,
ProgramHash *hashOut);
egl::BlobCache::Key *hashOut);
// Empty the cache.
void clear();
......@@ -114,14 +97,7 @@ class MemoryProgramCache final : angle::NonCopyable
size_t maxSize() const;
private:
enum class CacheSource
{
PutProgram,
PutBinary,
};
using CacheEntry = std::pair<angle::MemoryBuffer, CacheSource>;
angle::SizedMRUCache<ProgramHash, CacheEntry> mProgramBinaryCache;
egl::BlobCache &mBlobCache;
unsigned int mIssuedWarnings;
};
......
......@@ -600,7 +600,7 @@ struct Program::LinkingState
{
const Context *context;
std::unique_ptr<ProgramLinkedResources> resources;
ProgramHash programHash;
egl::BlobCache::Key programHash;
std::unique_ptr<rx::LinkEvent> linkEvent;
};
......@@ -1114,7 +1114,7 @@ Error Program::link(const gl::Context *context)
return NoError();
}
ProgramHash programHash = {0};
egl::BlobCache::Key programHash = {0};
MemoryProgramCache *cache = context->getMemoryProgramCache();
if (cache)
......
......@@ -55,13 +55,13 @@ class SizedMRUCache final : angle::NonCopyable
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())
{
auto it = mStore.begin();
std::advance(it, index);
*keyOut = it->first;
*keyOut = &it->first;
*valueOut = &it->second.value;
return true;
}
......@@ -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
// 4 bytes, since the PMurhHas32 method can only operate increments of 4-byte words.
// Computes a hash of "key". Any data passed to this function must be multiples of
// 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>
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");
return PMurHash32(seed, &key, sizeof(T));
return ComputeGenericHash(&key, sizeof(key));
}
} // namespace angle
......
......@@ -5,9 +5,10 @@
//
// SizedMRUCache_unittest.h: Unit tests for the sized MRU cached.
#include "libANGLE/SizedMRUCache.h"
#include <gtest/gtest.h>
#include "libANGLE/SizedMRUCache.h"
namespace angle
{
......
......@@ -75,6 +75,8 @@ class DisplayImpl : public EGLImplFactory
virtual gl::Version getMaxSupportedESVersion() const = 0;
const egl::Caps &getCaps() const;
virtual void setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get) {}
const egl::DisplayExtensions &getExtensions() const;
protected:
......
......@@ -139,4 +139,11 @@ void DisplayEGL::generateCaps(egl::Caps *outCaps) const
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
......@@ -28,6 +28,8 @@ class DisplayEGL : public DisplayGL
std::string getVendorString() const override;
void setBlobCacheFuncs(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get) override;
virtual void destroyNativeContext(EGLContext context) = 0;
protected:
......
......@@ -66,7 +66,9 @@ struct FunctionsEGL::EGLDispatchTable
swapBuffersWithDamageKHRPtr(nullptr),
presentationTimeANDROIDPtr(nullptr)
presentationTimeANDROIDPtr(nullptr),
setBlobCacheFuncsANDROIDPtr(nullptr)
{
}
......@@ -108,6 +110,9 @@ struct FunctionsEGL::EGLDispatchTable
// EGL_ANDROID_presentation_time
PFNEGLPRESENTATIONTIMEANDROIDPROC presentationTimeANDROIDPtr;
// EGL_ANDROID_blob_cache
PFNEGLSETBLOBCACHEFUNCSANDROIDPROC setBlobCacheFuncsANDROIDPtr;
};
FunctionsEGL::FunctionsEGL()
......@@ -197,6 +202,11 @@ egl::Error FunctionsEGL::initialize(EGLNativeDisplayType nativeDisplay)
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
return egl::NoError();
......@@ -369,4 +379,10 @@ EGLBoolean FunctionsEGL::presentationTimeANDROID(EGLSurface surface, EGLnsecsAND
{
return mFnPtrs->presentationTimeANDROIDPtr(mEGLDisplay, surface, time);
}
void FunctionsEGL::setBlobCacheFuncsANDROID(EGLSetBlobFuncANDROID set,
EGLGetBlobFuncANDROID get) const
{
return mFnPtrs->setBlobCacheFuncsANDROIDPtr(mEGLDisplay, set, get);
}
} // namespace rx
......@@ -79,6 +79,8 @@ class FunctionsEGL
EGLBoolean presentationTimeANDROID(EGLSurface surface, EGLnsecsANDROID time) const;
void setBlobCacheFuncsANDROID(EGLSetBlobFuncANDROID set, EGLGetBlobFuncANDROID get) const;
private:
// So as to isolate from angle we do not include angleutils.h and cannot
// use angle::NonCopyable so we replicated it here instead.
......
......@@ -2715,6 +2715,26 @@ Error ValidatePresentationTimeANDROID(const Display *display,
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)
{
ANGLE_TRY(ValidateConfig(display, config));
......@@ -2842,7 +2862,7 @@ Error ValidateProgramCacheQueryANGLE(const Display *display,
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.";
}
......@@ -2868,7 +2888,7 @@ Error ValidateProgramCachePopulateANGLE(const Display *display,
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.";
}
......
......@@ -174,6 +174,10 @@ Error ValidatePresentationTimeANDROID(const Display *display,
const Surface *surface,
EGLnsecsANDROID time);
Error ValidateSetBlobCacheANDROID(const Display *display,
EGLSetBlobFuncANDROID set,
EGLGetBlobFuncANDROID get);
Error ValidateGetConfigAttrib(const Display *display, const Config *config, EGLint attribute);
Error ValidateChooseConfig(const Display *display,
const AttributeMap &attribs,
......
......@@ -511,4 +511,11 @@ EGLint EGLAPIENTRY eglLabelObjectKHR(EGLDisplay dpy,
return egl::LabelObjectKHR(dpy, objectType, object, label);
}
void EGLAPIENTRY eglSetBlobCacheFuncsANDROID(EGLDisplay dpy,
EGLSetBlobFuncANDROID set,
EGLGetBlobFuncANDROID get)
{
return egl::SetBlobCacheFuncsANDROID(dpy, set, get);
}
} // extern "C"
......@@ -69,6 +69,7 @@ EXPORTS
eglDebugMessageControlKHR @75
eglQueryDebugKHR @76
eglLabelObjectKHR @77
eglSetBlobCacheFuncsANDROID @78
; 1.5 entry points
eglCreateSync @38
......
......@@ -116,6 +116,8 @@ libangle_sources = [
"src/libANGLE/AttributeMap.cpp",
"src/libANGLE/AttributeMap.h",
"src/libANGLE/BinaryStream.h",
"src/libANGLE/BlobCache.cpp",
"src/libANGLE/BlobCache.h",
"src/libANGLE/Buffer.cpp",
"src/libANGLE/Buffer.h",
"src/libANGLE/Caps.cpp",
......
......@@ -949,6 +949,25 @@ EGLBoolean EGLAPIENTRY PresentationTimeANDROID(EGLDisplay dpy,
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)
{
ANGLE_SCOPED_GLOBAL_LOCK();
......
......@@ -120,6 +120,11 @@ ANGLE_EXPORT EGLBoolean EGLAPIENTRY PresentationTimeANDROID(EGLDisplay dpy,
EGLSurface surface,
EGLnsecsANDROID time);
// EGL_ANDRIOD_blob_cache
ANGLE_EXPORT void EGLAPIENTRY SetBlobCacheFuncsANDROID(EGLDisplay dpy,
EGLSetBlobFuncANDROID set,
EGLGetBlobFuncANDROID get);
// EGL_ANGLE_program_cache_control
ANGLE_EXPORT EGLint EGLAPIENTRY ProgramCacheGetAttribANGLE(EGLDisplay dpy, EGLenum attrib);
ANGLE_EXPORT void EGLAPIENTRY ProgramCacheQueryANGLE(EGLDisplay dpy,
......
......@@ -89,6 +89,7 @@ ProcEntry g_procTable[] = {
{"eglReleaseDeviceANGLE", P(egl::ReleaseDeviceANGLE)},
{"eglReleaseTexImage", P(egl::ReleaseTexImage)},
{"eglReleaseThread", P(egl::ReleaseThread)},
{"eglSetBlobCacheFuncsANDROID", P(egl::SetBlobCacheFuncsANDROID)},
{"eglStreamAttribKHR", P(egl::StreamAttribKHR)},
{"eglStreamConsumerAcquireKHR", P(egl::StreamConsumerAcquireKHR)},
{"eglStreamConsumerGLTextureExternalAttribsNV",
......@@ -1241,5 +1242,5 @@ ProcEntry g_procTable[] = {
{"glWeightPointerOES", P(gl::WeightPointerOES)},
{"glWeightPointerOESContextANGLE", P(gl::WeightPointerOESContextANGLE)}};
size_t g_numProcs = 1173;
size_t g_numProcs = 1174;
} // namespace egl
......@@ -840,6 +840,10 @@
"eglPresentationTimeANDROID"
],
"EGL_ANDROID_blob_cache": [
"eglSetBlobCacheFuncsANDROID"
],
"EGL_ANGLE_program_cache_control": [
"eglProgramCacheGetAttribANGLE",
"eglProgramCacheQueryANGLE",
......
......@@ -117,6 +117,7 @@ angle_end2end_tests_sources = [
"gl_tests/WebGLCompatibilityTest.cpp",
"gl_tests/WebGLFramebufferTest.cpp",
"gl_tests/WebGLReadOutsideFramebufferTest.cpp",
"egl_tests/EGLBlobCacheTest.cpp",
"egl_tests/EGLContextCompatibilityTest.cpp",
"egl_tests/EGLContextSharingTest.cpp",
"egl_tests/EGLDebugTest.cpp",
......
......@@ -15,6 +15,7 @@ angle_unittests_sources = [
"../common/vector_utils_unittest.cpp",
"../gpu_info_util/SystemInfo_unittest.cpp",
"../libANGLE/BinaryStream_unittest.cpp",
"../libANGLE/BlobCache_unittest.cpp",
"../libANGLE/Config_unittest.cpp",
"../libANGLE/Fence_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
{
mPlatformMethods.cacheProgram = &TestCacheProgram;
ANGLETestBase::ANGLETestSetUp();
ANGLETest::SetUp();
if (extensionAvailable())
{
......@@ -48,7 +48,7 @@ class EGLProgramCacheControlTest : public ANGLETest
getEGLWindow()->initializeContext();
}
void TearDown() override { ANGLETestBase::ANGLETestTearDown(); }
void TearDown() override { ANGLETest::TearDown(); }
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