Commit c43be720 by Jamie Madill Committed by Commit Bot

Implement ANGLE_program_cache_control extensions.

This will give the browsers the ability to control the cache size, query and populate the contents, and trim cache contents on memory pressure. BUG=angleproject:1897 Change-Id: I6edaa7d307b890223db98792d5b074e4a7fdfaa4 Reviewed-on: https://chromium-review.googlesource.com/563606 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org>
parent bc58515e
......@@ -14,15 +14,7 @@
namespace gl
{
// The size to set for the program cache for default and low-end device cases.
// Temporarily disabled to prevent double memory use in Chrome.
// TODO(jmadill): Re-enable once we have memory cache control.
//#if !defined(ANGLE_PLATFORM_ANDROID)
// const size_t kDefaultMaxProgramCacheMemoryBytes = 6 * 1024 * 1024;
//#else
// const size_t kDefaultMaxProgramCacheMemoryBytes = 2 * 1024 * 1024;
// const size_t kLowEndMaxProgramCacheMemoryBytes = 512 * 1024;
//#endif
// The binary cache is currently left disable by default, and the application can enable it.
const size_t kDefaultMaxProgramCacheMemoryBytes = 0;
enum
......
......@@ -283,7 +283,8 @@ Context::Context(rx::EGLImplFactory *implFactory,
initWorkarounds();
mGLState.initialize(this, GetDebug(attribs), GetBindGeneratesResource(attribs),
GetClientArraysEnabled(attribs), robustResourceInit);
GetClientArraysEnabled(attribs), robustResourceInit,
mMemoryProgramCache != nullptr);
mFenceNVHandleAllocator.setBaseHandle(0);
......@@ -2673,6 +2674,9 @@ void Context::initCaps(const egl::DisplayExtensions &displayExtensions)
mExtensions.robustResourceInitialization =
egl::Display::GetClientExtensions().displayRobustResourceInitialization;
// Enable the cache control query unconditionally.
mExtensions.programCacheControl = true;
// Apply implementation limits
mCaps.maxVertexAttributes = std::min<GLuint>(mCaps.maxVertexAttributes, MAX_VERTEX_ATTRIBS);
mCaps.maxVertexAttribBindings =
......
......@@ -434,6 +434,13 @@ bool ValidationContext::getQueryParameterInfo(GLenum pname, GLenum *type, unsign
return true;
}
if (getExtensions().programCacheControl && pname == GL_PROGRAM_CACHE_ENABLED_ANGLE)
{
*type = GL_BOOL;
*numParams = 1;
return true;
}
// Check for ES3.0+ parameter names which are also exposed as ES2 extensions
switch (pname)
{
......
......@@ -741,10 +741,22 @@ Error Display::createContext(const Config *configuration,
shareTextures = mTextureManager;
}
// Memory cache temporarily disabled to prevent double memory use in Chrome.
// TODO(jmadill): Re-enable once we have memory cache control.
gl::MemoryProgramCache *cachePointer = &mMemoryProgramCache;
// Check context creation attributes to see if we should enable the cache.
if (mAttributeMap.get(EGL_CONTEXT_PROGRAM_BINARY_CACHE_ENABLED_ANGLE, EGL_TRUE) == EGL_FALSE)
{
cachePointer = nullptr;
}
// A program cache size of zero indicates it should be disabled.
if (mMemoryProgramCache.maxSize() == 0)
{
cachePointer = nullptr;
}
gl::Context *context =
new gl::Context(mImplementation, configuration, shareContext, shareTextures, nullptr,
new gl::Context(mImplementation, configuration, shareContext, shareTextures, cachePointer,
attribs, mDisplayExtensions, isRobustResourceInitEnabled());
ASSERT(context != nullptr);
......@@ -1014,6 +1026,9 @@ void Display::initDisplayExtensions()
// Force EGL_KHR_get_all_proc_addresses on.
mDisplayExtensions.getAllProcAddresses = true;
// Enable program cache control since it is not back-end dependent.
mDisplayExtensions.programCacheControl = true;
mDisplayExtensionString = GenerateExtensionsString(mDisplayExtensions);
}
......@@ -1110,4 +1125,100 @@ bool Display::isRobustResourceInitEnabled() const
EGL_TRUE);
}
EGLint Display::programCacheGetAttrib(EGLenum attrib) const
{
switch (attrib)
{
case EGL_PROGRAM_CACHE_KEY_LENGTH_ANGLE:
return static_cast<EGLint>(gl::kProgramHashLength);
case EGL_PROGRAM_CACHE_SIZE_ANGLE:
return static_cast<EGLint>(mMemoryProgramCache.entryCount());
default:
UNREACHABLE();
return 0;
}
}
Error Display::programCacheQuery(EGLint index,
void *key,
EGLint *keysize,
void *binary,
EGLint *binarysize)
{
ASSERT(index >= 0 && index < static_cast<EGLint>(mMemoryProgramCache.entryCount()));
const angle::MemoryBuffer *programBinary = nullptr;
gl::ProgramHash programHash;
// TODO(jmadill): Make this thread-safe.
bool result =
mMemoryProgramCache.getAt(static_cast<size_t>(index), &programHash, &programBinary);
if (!result)
{
return EglBadAccess() << "Program binary not accessible.";
}
ASSERT(keysize && binarysize);
if (key)
{
ASSERT(*keysize == static_cast<EGLint>(gl::kProgramHashLength));
memcpy(key, programHash.data(), gl::kProgramHashLength);
}
if (binary)
{
// 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))
{
return EglBadAccess() << "Program binary too large or changed during access.";
}
memcpy(binary, programBinary->data(), programBinary->size());
}
*binarysize = static_cast<EGLint>(programBinary->size());
*keysize = static_cast<EGLint>(gl::kProgramHashLength);
return NoError();
}
Error Display::programCachePopulate(const void *key,
EGLint keysize,
const void *binary,
EGLint binarysize)
{
ASSERT(keysize == static_cast<EGLint>(gl::kProgramHashLength));
gl::ProgramHash programHash;
memcpy(programHash.data(), key, gl::kProgramHashLength);
mMemoryProgramCache.putBinary(programHash, reinterpret_cast<const uint8_t *>(binary),
static_cast<size_t>(binarysize));
return NoError();
}
EGLint Display::programCacheResize(EGLint limit, EGLenum mode)
{
switch (mode)
{
case EGL_PROGRAM_CACHE_RESIZE_ANGLE:
{
size_t initialSize = mMemoryProgramCache.size();
mMemoryProgramCache.resize(static_cast<size_t>(limit));
return static_cast<EGLint>(initialSize);
}
case EGL_PROGRAM_CACHE_TRIM_ANGLE:
return static_cast<EGLint>(mMemoryProgramCache.trim(static_cast<size_t>(limit)));
default:
UNREACHABLE();
return 0;
}
}
} // namespace egl
......@@ -48,6 +48,9 @@ struct DisplayState final : private angle::NonCopyable
SurfaceSet surfaceSet;
};
// Constant coded here as a sanity limit.
constexpr EGLAttrib kProgramCacheSizeAbsoluteMax = 0x4000000;
class Display final : angle::NonCopyable
{
public:
......@@ -132,6 +135,18 @@ class Display final : angle::NonCopyable
const std::string &getExtensionString() const;
const std::string &getVendorString() const;
EGLint programCacheGetAttrib(EGLenum attrib) const;
Error programCacheQuery(EGLint index,
void *key,
EGLint *keysize,
void *binary,
EGLint *binarysize);
Error programCachePopulate(const void *key,
EGLint keysize,
const void *binary,
EGLint binarysize);
EGLint programCacheResize(EGLint limit, EGLenum mode);
const AttributeMap &getAttributeMap() const { return mAttributeMap; }
EGLNativeDisplayType getNativeDisplayId() const { return mDisplayId; }
......
......@@ -547,6 +547,13 @@ bool MemoryProgramCache::get(const ProgramHash &programHash, const angle::Memory
return mProgramBinaryCache.get(programHash, programOut);
}
bool MemoryProgramCache::getAt(size_t index,
ProgramHash *hashOut,
const angle::MemoryBuffer **programOut)
{
return mProgramBinaryCache.getAt(index, hashOut, programOut);
}
void MemoryProgramCache::remove(const ProgramHash &programHash)
{
bool result = mProgramBinaryCache.eraseByKey(programHash);
......@@ -554,7 +561,6 @@ void MemoryProgramCache::remove(const ProgramHash &programHash)
}
void MemoryProgramCache::put(const ProgramHash &program,
const Context *context,
angle::MemoryBuffer &&binaryProgram)
{
const angle::MemoryBuffer *result =
......@@ -576,11 +582,10 @@ void MemoryProgramCache::putProgram(const ProgramHash &programHash,
{
angle::MemoryBuffer binaryProgram;
Serialize(context, program, &binaryProgram);
put(programHash, context, std::move(binaryProgram));
put(programHash, std::move(binaryProgram));
}
void MemoryProgramCache::putBinary(const Context *context,
const Program *program,
void MemoryProgramCache::putBinary(const ProgramHash &programHash,
const uint8_t *binary,
size_t length)
{
......@@ -589,12 +594,13 @@ void MemoryProgramCache::putBinary(const Context *context,
binaryProgram.resize(length);
memcpy(binaryProgram.data(), binary, length);
// Compute the hash.
ProgramHash programHash;
ComputeHash(context, program, &programHash);
// Store the binary.
put(programHash, context, std::move(binaryProgram));
const angle::MemoryBuffer *result =
mProgramBinaryCache.put(programHash, std::move(binaryProgram), binaryProgram.size());
if (!result)
{
ERR() << "Failed to store binary program in memory cache, program is too large.";
}
}
void MemoryProgramCache::clear()
......@@ -603,4 +609,29 @@ void MemoryProgramCache::clear()
mIssuedWarnings = 0;
}
void MemoryProgramCache::resize(size_t maxCacheSizeBytes)
{
mProgramBinaryCache.resize(maxCacheSizeBytes);
}
size_t MemoryProgramCache::entryCount() const
{
return mProgramBinaryCache.entryCount();
}
size_t MemoryProgramCache::trim(size_t limit)
{
return mProgramBinaryCache.shrinkToSize(limit);
}
size_t MemoryProgramCache::size() const
{
return mProgramBinaryCache.size();
}
size_t MemoryProgramCache::maxSize() const
{
return mProgramBinaryCache.maxSize();
}
} // namespace gl
......@@ -19,7 +19,8 @@
namespace gl
{
// 160-bit SHA-1 hash key.
using ProgramHash = std::array<uint8_t, 20>;
constexpr size_t kProgramHashLength = 20;
using ProgramHash = std::array<uint8_t, kProgramHashLength>;
} // namespace gl
namespace std
......@@ -72,17 +73,17 @@ class MemoryProgramCache final : angle::NonCopyable
// Check if the cache contains a binary matching the specified program.
bool get(const ProgramHash &programHash, const angle::MemoryBuffer **programOut);
// For querying the contents of the cache.
bool getAt(size_t index, ProgramHash *hashOut, const angle::MemoryBuffer **programOut);
// Evict a program from the binary cache.
void remove(const ProgramHash &programHash);
// Helper method that serializes a program.
void putProgram(const ProgramHash &programHash, const Context *context, const Program *program);
// Helper method that copies a user binary.
void putBinary(const Context *context,
const Program *program,
const uint8_t *binary,
size_t length);
// Store a binary directly.
void putBinary(const ProgramHash &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.
......@@ -94,10 +95,24 @@ class MemoryProgramCache final : angle::NonCopyable
// Empty the cache.
void clear();
// Resize the cache. Discards current contents.
void resize(size_t maxCacheSizeBytes);
// Returns the number of entries in the cache.
size_t entryCount() const;
// Reduces the current cache size and returns the number of bytes freed.
size_t trim(size_t limit);
// Returns the current cache size in bytes.
size_t size() const;
// Returns the maximum cache size in bytes.
size_t maxSize() const;
private:
// Insert or update a binary program. Program contents are transferred.
void put(const ProgramHash &programHash,
const Context *context,
angle::MemoryBuffer &&binaryProgram);
angle::SizedMRUCache<ProgramHash, angle::MemoryBuffer> mProgramBinaryCache;
......
......@@ -39,13 +39,7 @@ class SizedMRUCache final : angle::NonCopyable
auto retVal = mStore.Put(key, ValueAndSize(std::move(value), size));
mCurrentSize += size;
while (mCurrentSize > mMaximumTotalSize)
{
ASSERT(!mStore.empty());
auto iter = mStore.rbegin();
mCurrentSize -= iter->second.size;
mStore.Erase(iter);
}
shrinkToSize(mMaximumTotalSize);
return &retVal->second.value;
}
......@@ -61,6 +55,20 @@ class SizedMRUCache final : angle::NonCopyable
return true;
}
bool getAt(size_t index, Key *keyOut, const Value **valueOut)
{
if (index < mStore.size())
{
auto it = mStore.begin();
std::advance(it, index);
*keyOut = it->first;
*valueOut = &it->second.value;
return true;
}
*valueOut = nullptr;
return false;
}
bool empty() const { return mStore.empty(); }
void clear()
......@@ -83,8 +91,35 @@ class SizedMRUCache final : angle::NonCopyable
return false;
}
size_t entryCount() const { return mStore.size(); }
size_t size() const { return mCurrentSize; }
// Also discards the cache contents.
void resize(size_t maximumTotalSize)
{
clear();
mMaximumTotalSize = maximumTotalSize;
}
// Reduce current memory usage.
size_t shrinkToSize(size_t limit)
{
size_t initialSize = mCurrentSize;
while (mCurrentSize > limit)
{
ASSERT(!mStore.empty());
auto iter = mStore.rbegin();
mCurrentSize -= iter->second.size;
mStore.Erase(iter);
}
return (initialSize - mCurrentSize);
}
size_t maxSize() const { return mMaximumTotalSize; }
private:
struct ValueAndSize
{
......
......@@ -63,7 +63,8 @@ State::State()
mMultiSampling(false),
mSampleAlphaToOne(false),
mFramebufferSRGB(true),
mRobustResourceInit(false)
mRobustResourceInit(false),
mProgramBinaryCacheEnabled(false)
{
}
......@@ -75,7 +76,8 @@ void State::initialize(const Context *context,
bool debug,
bool bindGeneratesResource,
bool clientArraysEnabled,
bool robustResourceInit)
bool robustResourceInit,
bool programBinaryCacheEnabled)
{
const Caps &caps = context->getCaps();
const Extensions &extensions = context->getExtensions();
......@@ -186,6 +188,7 @@ void State::initialize(const Context *context,
mPathStencilMask = std::numeric_limits<GLuint>::max();
mRobustResourceInit = robustResourceInit;
mProgramBinaryCacheEnabled = programBinaryCacheEnabled;
}
void State::reset(const Context *context)
......@@ -687,7 +690,12 @@ bool State::getEnableFeature(GLenum feature) const
return getFramebufferSRGB();
case GL_CONTEXT_ROBUST_RESOURCE_INITIALIZATION_ANGLE:
return mRobustResourceInit;
default: UNREACHABLE(); return false;
case GL_PROGRAM_CACHE_ENABLED_ANGLE:
return mProgramBinaryCacheEnabled;
default:
UNREACHABLE();
return false;
}
}
......@@ -1666,6 +1674,10 @@ void State::getBooleanv(GLenum pname, GLboolean *params)
case GL_CONTEXT_ROBUST_RESOURCE_INITIALIZATION_ANGLE:
*params = mRobustResourceInit ? GL_TRUE : GL_FALSE;
break;
case GL_PROGRAM_CACHE_ENABLED_ANGLE:
*params = mProgramBinaryCacheEnabled ? GL_TRUE : GL_FALSE;
break;
default:
UNREACHABLE();
break;
......
......@@ -45,7 +45,8 @@ class State : angle::NonCopyable
bool debug,
bool bindGeneratesResource,
bool clientArraysEnabled,
bool robustResourceInit);
bool robustResourceInit,
bool programBinaryCacheEnabled);
void reset(const Context *context);
// State chunk getters
......@@ -569,6 +570,9 @@ class State : angle::NonCopyable
// GL_ANGLE_robust_resource_intialization
bool mRobustResourceInit;
// GL_ANGLE_program_cache_control
bool mProgramBinaryCacheEnabled;
DirtyBits mDirtyBits;
DirtyObjects mDirtyObjects;
};
......
......@@ -714,6 +714,20 @@ Error ValidateCreateContext(Display *display, Config *configuration, gl::Context
}
break;
case EGL_CONTEXT_PROGRAM_BINARY_CACHE_ENABLED_ANGLE:
if (!display->getExtensions().programCacheControl)
{
return EglBadAttribute()
<< "Attribute EGL_CONTEXT_PROGRAM_BINARY_CACHE_ENABLED_ANGLE "
"requires EGL_ANGLE_program_cache_control.";
}
if (value != EGL_TRUE && value != EGL_FALSE)
{
return EglBadAttribute() << "EGL_CONTEXT_PROGRAM_BINARY_CACHE_ENABLED_ANGLE must "
"be EGL_TRUE or EGL_FALSE.";
}
break;
default:
return EglBadAttribute() << "Unknown attribute.";
}
......@@ -2158,4 +2172,122 @@ Error ValidateGetPlatformDisplayEXT(EGLenum platform,
return ValidateGetPlatformDisplayCommon(platform, native_display, attribMap);
}
Error ValidateProgramCacheGetAttribANGLE(const Display *display, EGLenum attrib)
{
ANGLE_TRY(ValidateDisplay(display));
if (!display->getExtensions().programCacheControl)
{
return EglBadAccess() << "Extension not supported";
}
switch (attrib)
{
case EGL_PROGRAM_CACHE_KEY_LENGTH_ANGLE:
case EGL_PROGRAM_CACHE_SIZE_ANGLE:
break;
default:
return EglBadParameter() << "Invalid program cache attribute.";
}
return NoError();
}
Error ValidateProgramCacheQueryANGLE(const Display *display,
EGLint index,
void *key,
EGLint *keysize,
void *binary,
EGLint *binarysize)
{
ANGLE_TRY(ValidateDisplay(display));
if (!display->getExtensions().programCacheControl)
{
return EglBadAccess() << "Extension not supported";
}
if (index < 0 || index >= display->programCacheGetAttrib(EGL_PROGRAM_CACHE_SIZE_ANGLE))
{
return EglBadParameter() << "Program index out of range.";
}
if (keysize == nullptr || binarysize == nullptr)
{
return EglBadParameter() << "keysize and binarysize must always be valid pointers.";
}
if (binary && *keysize != static_cast<EGLint>(gl::kProgramHashLength))
{
return EglBadParameter() << "Invalid program key size.";
}
if ((key == nullptr) != (binary == nullptr))
{
return EglBadParameter() << "key and binary must both be null or both non-null.";
}
return NoError();
}
Error ValidateProgramCachePopulateANGLE(const Display *display,
const void *key,
EGLint keysize,
const void *binary,
EGLint binarysize)
{
ANGLE_TRY(ValidateDisplay(display));
if (!display->getExtensions().programCacheControl)
{
return EglBadAccess() << "Extension not supported";
}
if (keysize != static_cast<EGLint>(gl::kProgramHashLength))
{
return EglBadParameter() << "Invalid program key size.";
}
if (key == nullptr || binary == nullptr)
{
return EglBadParameter() << "null pointer in arguments.";
}
// Upper bound for binarysize is arbitrary.
if (binarysize <= 0 || binarysize > egl::kProgramCacheSizeAbsoluteMax)
{
return EglBadParameter() << "binarysize out of valid range.";
}
return NoError();
}
Error ValidateProgramCacheResizeANGLE(const Display *display, EGLint limit, EGLenum mode)
{
ANGLE_TRY(ValidateDisplay(display));
if (!display->getExtensions().programCacheControl)
{
return EglBadAccess() << "Extension not supported";
}
if (limit < 0)
{
return EglBadParameter() << "limit must be non-negative.";
}
switch (mode)
{
case EGL_PROGRAM_CACHE_RESIZE_ANGLE:
case EGL_PROGRAM_CACHE_TRIM_ANGLE:
break;
default:
return EglBadParameter() << "Invalid cache resize mode.";
}
return NoError();
}
} // namespace egl
......@@ -129,6 +129,41 @@ Error ValidateGetPlatformDisplay(EGLenum platform,
Error ValidateGetPlatformDisplayEXT(EGLenum platform,
void *native_display,
const EGLint *attrib_list);
Error ValidateProgramCacheGetAttribANGLE(const Display *display, EGLenum attrib);
Error ValidateProgramCacheQueryANGLE(const Display *display,
EGLint index,
void *key,
EGLint *keysize,
void *binary,
EGLint *binarysize);
Error ValidateProgramCachePopulateANGLE(const Display *display,
const void *key,
EGLint keysize,
const void *binary,
EGLint binarysize);
Error ValidateProgramCacheResizeANGLE(const Display *display, EGLint limit, EGLenum mode);
} // namespace egl
#define ANGLE_EGL_TRY(THREAD, EXPR) \
{ \
auto ANGLE_LOCAL_VAR = (EXPR); \
if (ANGLE_LOCAL_VAR.isError()) \
return THREAD->setError(ANGLE_LOCAL_VAR); \
}
#define ANGLE_EGL_TRY_RETURN(THREAD, EXPR, RETVAL) \
{ \
auto ANGLE_LOCAL_VAR = (EXPR); \
if (ANGLE_LOCAL_VAR.isError()) \
{ \
THREAD->setError(ANGLE_LOCAL_VAR); \
return RETVAL; \
} \
}
#endif // LIBANGLE_VALIDATIONEGL_H_
......@@ -81,7 +81,7 @@ TEST(ValidationESTest, DISABLED_DrawElementsWithMaxIndexGivesError)
caps.maxElementIndex = 100;
caps.maxDrawBuffers = 1;
caps.maxColorAttachments = 1;
state.initialize(nullptr, false, true, true, false);
state.initialize(nullptr, false, true, true, false, false);
NiceMock<MockTextureImpl> *textureImpl = new NiceMock<MockTextureImpl>();
EXPECT_CALL(mockFactory, createTexture(_)).WillOnce(Return(textureImpl));
......
......@@ -790,8 +790,12 @@ EGLint EGLAPIENTRY ProgramCacheGetAttribANGLE(EGLDisplay dpy, EGLenum attrib)
{
EVENT("(EGLDisplay dpy = 0x%0.8p, EGLenum attrib = 0x%X)", dpy, attrib);
UNIMPLEMENTED();
return 0;
Display *display = static_cast<Display *>(dpy);
Thread *thread = GetCurrentThread();
ANGLE_EGL_TRY_RETURN(thread, ValidateProgramCacheGetAttribANGLE(display, attrib), 0);
return display->programCacheGetAttrib(attrib);
}
void EGLAPIENTRY ProgramCacheQueryANGLE(EGLDisplay dpy,
......@@ -806,7 +810,13 @@ void EGLAPIENTRY ProgramCacheQueryANGLE(EGLDisplay dpy,
"0x%0.8p, void *binary = 0x%0.8p, EGLint *size = 0x%0.8p)",
dpy, index, key, keysize, binary, binarysize);
UNIMPLEMENTED();
Display *display = static_cast<Display *>(dpy);
Thread *thread = GetCurrentThread();
ANGLE_EGL_TRY(thread,
ValidateProgramCacheQueryANGLE(display, index, key, keysize, binary, binarysize));
ANGLE_EGL_TRY(thread, display->programCacheQuery(index, key, keysize, binary, binarysize));
}
void EGLAPIENTRY ProgramCachePopulateANGLE(EGLDisplay dpy,
......@@ -820,14 +830,25 @@ void EGLAPIENTRY ProgramCachePopulateANGLE(EGLDisplay dpy,
"0x%0.8p, EGLint *size = 0x%0.8p)",
dpy, key, keysize, binary, binarysize);
UNIMPLEMENTED();
Display *display = static_cast<Display *>(dpy);
Thread *thread = GetCurrentThread();
ANGLE_EGL_TRY(thread,
ValidateProgramCachePopulateANGLE(display, key, keysize, binary, binarysize));
ANGLE_EGL_TRY(thread, display->programCachePopulate(key, keysize, binary, binarysize));
}
EGLint EGLAPIENTRY ProgramCacheResizeANGLE(EGLDisplay dpy, EGLint limit, EGLenum mode)
{
EVENT("(EGLDisplay dpy = 0x%0.8p, EGLint limit = %d, EGLenum mode = 0x%X)", dpy, limit, mode);
UNIMPLEMENTED();
return 0;
Display *display = static_cast<Display *>(dpy);
Thread *thread = GetCurrentThread();
ANGLE_EGL_TRY_RETURN(thread, ValidateProgramCacheResizeANGLE(display, limit, mode), 0);
return display->programCacheResize(limit, mode);
}
} // namespace egl
......@@ -56,6 +56,11 @@ class EGLProgramCacheControlTest : public ANGLETest
return eglDisplayExtensionEnabled(display, kEGLExtName);
}
bool programBinaryAvailable()
{
return (getClientMajorVersion() >= 3 || extensionEnabled("GL_OES_get_program_binary"));
}
ProgramKeyType mCachedKey;
std::vector<uint8_t> mCachedBinary;
};
......@@ -169,7 +174,7 @@ TEST_P(EGLProgramCacheControlTest, NegativeAPI)
// Tests a basic use case.
TEST_P(EGLProgramCacheControlTest, SaveAndReload)
{
ANGLE_SKIP_TEST_IF(!extensionAvailable());
ANGLE_SKIP_TEST_IF(!extensionAvailable() || !programBinaryAvailable());
const std::string vertexShader =
"attribute vec4 position; void main() { gl_Position = position; }";
......
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