Add GrContext::oomed() and implement for GL and VK.

Surfaces to client whether GrContext has seen a GL_OUT_MEMORY,
VK_ERROR_OUT_OF_HOST_MEMORY, or VK_ERROR_OUT_OF_DEVICE_MEMORY error.

Bug: chromium:1093997
Change-Id: I8e9799a0f7d8a74df056629d7d1d07c0d0a0fe30
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/298216
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
Brian Salomon 2020-06-24 10:19:52 -04:00 committed by Skia Commit-Bot
parent 03b92cd667
commit 24069ebd23
24 changed files with 392 additions and 109 deletions

View File

@ -7,7 +7,9 @@ This file includes a list of high level updates for each milestone release.
Milestone 85 Milestone 85
------------ ------------
* <insert new release notes here> * Added GrContext::oomed() which reports whether Skia has seen a GL_OUT_OF_MEMORY
error from Open GL [ES] or VK_ERROR_OUT_OF_*_MEMORY from Vulkan.
https://review.skia.org/298216
* Add option on SkSurface::flush to pass in a GrBackendSurfaceMutableState which * Add option on SkSurface::flush to pass in a GrBackendSurfaceMutableState which
we will set the gpu backend surface to be at the end of the flush. we will set the gpu backend surface to be at the end of the flush.

View File

@ -103,6 +103,7 @@ tests_sources = [
"$_tests/GrCCPRTest.cpp", "$_tests/GrCCPRTest.cpp",
"$_tests/GrContextAbandonTest.cpp", "$_tests/GrContextAbandonTest.cpp",
"$_tests/GrContextFactoryTest.cpp", "$_tests/GrContextFactoryTest.cpp",
"$_tests/GrContextOOM.cpp",
"$_tests/GrFinishedFlushTest.cpp", "$_tests/GrFinishedFlushTest.cpp",
"$_tests/GrMemoryPoolTest.cpp", "$_tests/GrMemoryPoolTest.cpp",
"$_tests/GrMeshTest.cpp", "$_tests/GrMeshTest.cpp",

View File

@ -151,6 +151,23 @@ public:
*/ */
bool abandoned() override; bool abandoned() override;
/**
* Checks if the underlying 3D API reported an out-of-memory error. If this returns true it is
* reset and will return false until another out-of-memory error is reported by the 3D API. If
* the context is abandoned then this will report false.
*
* Currently this is implemented for:
*
* OpenGL [ES] - Note that client calls to glGetError() may swallow GL_OUT_OF_MEMORY errors and
* therefore hide the error from Skia. Also, it is not advised to use this in combination with
* enabling GrContextOptions::fSkipGLErrorChecks. That option may prevent GrContext from ever
* checking the GL context for OOM.
*
* Vulkan - Reports true if VK_ERROR_OUT_OF_HOST_MEMORY or VK_ERROR_OUT_OF_DEVICE_MEMORY has
* occurred.
*/
bool oomed();
/** /**
* This is similar to abandonContext() however the underlying 3D context is not yet lost and * This is similar to abandonContext() however the underlying 3D context is not yet lost and
* the GrContext will cleanup all allocated resources before returning. After returning it will * the GrContext will cleanup all allocated resources before returning. After returning it will

View File

@ -261,6 +261,11 @@ struct SK_API GrContextOptions {
*/ */
bool fClearAllTextures = false; bool fClearAllTextures = false;
/**
* Randomly generate a (false) GL_OUT_OF_MEMORY error
*/
bool fRandomGLOOM = false;
/** /**
* Include or exclude specific GPU path renderers. * Include or exclude specific GPU path renderers.
*/ */

View File

@ -49,6 +49,13 @@ struct SK_API GrGLInterface : public SkRefCnt {
private: private:
typedef SkRefCnt INHERITED; typedef SkRefCnt INHERITED;
#if GR_GL_CHECK_ERROR
// This is here to avoid having our debug code that checks for a GL error after most GL calls
// accidentally swallow an OOM that should be reported.
mutable bool fOOMed = false;
bool fSuppressErrorLogging = false;
#endif
public: public:
GrGLInterface(); GrGLInterface();
@ -57,6 +64,19 @@ public:
// extensions. // extensions.
bool validate() const; bool validate() const;
#if GR_GL_CHECK_ERROR
GrGLenum checkError(const char* location, const char* call) const;
bool checkAndResetOOMed() const;
void suppressErrorLogging();
#endif
#if GR_TEST_UTILS
GrGLInterface(const GrGLInterface& that)
: fStandard(that.fStandard)
, fExtensions(that.fExtensions)
, fFunctions(that.fFunctions) {}
#endif
// Indicates the type of GL implementation // Indicates the type of GL implementation
union { union {
GrGLStandard fStandard; GrGLStandard fStandard;

View File

@ -167,6 +167,8 @@ bool GrContext::abandoned() {
return false; return false;
} }
bool GrContext::oomed() { return fGpu ? fGpu->checkAndResetOOMed() : false; }
void GrContext::resetGLTextureBindings() { void GrContext::resetGLTextureBindings() {
if (this->abandoned() || this->backend() != GrBackendApi::kOpenGL) { if (this->abandoned() || this->backend() != GrBackendApi::kOpenGL) {
return; return;

View File

@ -715,6 +715,14 @@ bool GrGpu::submitToGpu(bool syncCpu) {
return submitted; return submitted;
} }
bool GrGpu::checkAndResetOOMed() {
if (fOOMed) {
fOOMed = false;
return true;
}
return false;
}
void GrGpu::callSubmittedProcs(bool success) { void GrGpu::callSubmittedProcs(bool success) {
for (int i = 0; i < fSubmittedProcs.count(); ++i) { for (int i = 0; i < fSubmittedProcs.count(); ++i) {
fSubmittedProcs[i].fProc(fSubmittedProcs[i].fContext, success); fSubmittedProcs[i].fProc(fSubmittedProcs[i].fContext, success);

View File

@ -388,6 +388,12 @@ public:
virtual void checkFinishProcs() = 0; virtual void checkFinishProcs() = 0;
/**
* Checks if we detected an OOM from the underlying 3D API and if so returns true and resets
* the internal OOM state to false. Otherwise, returns false.
*/
bool checkAndResetOOMed();
/** /**
* Put this texture in a safe and known state for use across multiple GrContexts. Depending on * Put this texture in a safe and known state for use across multiple GrContexts. Depending on
* the backend, this may return a GrSemaphore. If so, other contexts should wait on that * the backend, this may return a GrSemaphore. If so, other contexts should wait on that
@ -721,6 +727,8 @@ protected:
void didWriteToSurface(GrSurface* surface, GrSurfaceOrigin origin, const SkIRect* bounds, void didWriteToSurface(GrSurface* surface, GrSurfaceOrigin origin, const SkIRect* bounds,
uint32_t mipLevels = 1) const; uint32_t mipLevels = 1) const;
void setOOMed() { fOOMed = true; }
typedef SkTInternalLList<GrStagingBuffer> StagingBufferList; typedef SkTInternalLList<GrStagingBuffer> StagingBufferList;
const StagingBufferList& availableStagingBuffers() { return fAvailableStagingBuffers; } const StagingBufferList& availableStagingBuffers() { return fAvailableStagingBuffers; }
const StagingBufferList& activeStagingBuffers() { return fActiveStagingBuffers; } const StagingBufferList& activeStagingBuffers() { return fActiveStagingBuffers; }
@ -884,6 +892,8 @@ private:
}; };
SkSTArray<4, SubmittedProc> fSubmittedProcs; SkSTArray<4, SubmittedProc> fSubmittedProcs;
bool fOOMed = false;
friend class GrPathRendering; friend class GrPathRendering;
typedef SkRefCnt INHERITED; typedef SkRefCnt INHERITED;
}; };

View File

@ -31,6 +31,13 @@
#include "src/gpu/dawn/GrDawnGpu.h" #include "src/gpu/dawn/GrDawnGpu.h"
#endif #endif
#if GR_TEST_UTILS
# include "include/utils/SkRandom.h"
# if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS)
# include <sanitizer/lsan_interface.h>
# endif
#endif
#ifdef SK_DISABLE_REDUCE_OPLIST_SPLITTING #ifdef SK_DISABLE_REDUCE_OPLIST_SPLITTING
static const bool kDefaultReduceOpsTaskSplitting = false; static const bool kDefaultReduceOpsTaskSplitting = false;
#else #else
@ -135,10 +142,50 @@ sk_sp<GrContext> GrContext::MakeGL() {
return MakeGL(nullptr, defaultOptions); return MakeGL(nullptr, defaultOptions);
} }
#if GR_TEST_UTILS
GrGLFunction<GrGLGetErrorFn> make_get_error_with_random_oom(GrGLFunction<GrGLGetErrorFn> original) {
// A SkRandom and a GrGLFunction<GrGLGetErrorFn> are too big to be captured by a
// GrGLFunction<GrGLGetError> (surprise, surprise). So we make a context object and
// capture that by pointer. However, GrGLFunction doesn't support calling a destructor
// on the thing it captures. So we leak the context.
struct GetErrorContext {
SkRandom fRandom;
GrGLFunction<GrGLGetErrorFn> fGetError;
};
auto errorContext = new GetErrorContext;
#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS)
__lsan_ignore_object(errorContext);
#endif
errorContext->fGetError = original;
return GrGLFunction<GrGLGetErrorFn>([errorContext]() {
GrGLenum error = errorContext->fGetError();
if (error == GR_GL_NO_ERROR && (errorContext->fRandom.nextU() % 300) == 0) {
error = GR_GL_OUT_OF_MEMORY;
}
return error;
});
}
#endif
sk_sp<GrContext> GrContext::MakeGL(sk_sp<const GrGLInterface> glInterface, sk_sp<GrContext> GrContext::MakeGL(sk_sp<const GrGLInterface> glInterface,
const GrContextOptions& options) { const GrContextOptions& options) {
sk_sp<GrContext> context(new GrLegacyDirectContext(GrBackendApi::kOpenGL, options)); sk_sp<GrContext> context(new GrLegacyDirectContext(GrBackendApi::kOpenGL, options));
#if GR_TEST_UTILS
if (options.fRandomGLOOM) {
auto copy = sk_make_sp<GrGLInterface>(*glInterface);
copy->fFunctions.fGetError =
make_get_error_with_random_oom(glInterface->fFunctions.fGetError);
#if GR_GL_CHECK_ERROR
// Suppress logging GL errors since we'll be synthetically generating them.
copy->suppressErrorLogging();
#endif
glInterface = std::move(copy);
}
#endif
context->fGpu = GrGLGpu::Make(std::move(glInterface), options, context.get()); context->fGpu = GrGLGpu::Make(std::move(glInterface), options, context.get());
if (!context->init()) { if (!context->init()) {
return nullptr; return nullptr;

View File

@ -14,15 +14,17 @@
#define GL_CALL(X) GR_GL_CALL(this->glGpu()->glInterface(), X) #define GL_CALL(X) GR_GL_CALL(this->glGpu()->glInterface(), X)
#define GL_CALL_RET(RET, X) GR_GL_CALL_RET(this->glGpu()->glInterface(), RET, X) #define GL_CALL_RET(RET, X) GR_GL_CALL_RET(this->glGpu()->glInterface(), RET, X)
#if GR_GL_CHECK_ALLOC_WITH_GET_ERROR #define GL_ALLOC_CALL(call) \
#define CLEAR_ERROR_BEFORE_ALLOC(iface) GrGLClearErr(iface) [&] { \
#define GL_ALLOC_CALL(iface, call) GR_GL_CALL_NOERRCHECK(iface, call) if (this->glGpu()->glCaps().skipErrorChecks()) { \
#define CHECK_ALLOC_ERROR(iface) GR_GL_GET_ERROR(iface) GR_GL_CALL(this->glGpu()->glInterface(), call); \
#else return static_cast<GrGLenum>(GR_GL_NO_ERROR); \
#define CLEAR_ERROR_BEFORE_ALLOC(iface) } else { \
#define GL_ALLOC_CALL(iface, call) GR_GL_CALL(iface, call) this->glGpu()->clearErrorsAndCheckForOOM(); \
#define CHECK_ALLOC_ERROR(iface) GR_GL_NO_ERROR GR_GL_CALL_NOERRCHECK(this->glGpu()->glInterface(), call); \
#endif return this->glGpu()->getErrorAndCheckForOOM(); \
} \
}()
#ifdef SK_DEBUG #ifdef SK_DEBUG
#define VALIDATE() this->validate() #define VALIDATE() this->validate()
@ -109,13 +111,8 @@ GrGLBuffer::GrGLBuffer(GrGLGpu* gpu, size_t size, GrGpuBufferType intendedType,
GL_CALL(GenBuffers(1, &fBufferID)); GL_CALL(GenBuffers(1, &fBufferID));
if (fBufferID) { if (fBufferID) {
GrGLenum target = gpu->bindBuffer(fIntendedType, this); GrGLenum target = gpu->bindBuffer(fIntendedType, this);
CLEAR_ERROR_BEFORE_ALLOC(gpu->glInterface()); GrGLenum error = GL_ALLOC_CALL(BufferData(target, (GrGLsizeiptr)size, data, fUsage));
// make sure driver can allocate memory for this buffer if (error != GR_GL_NO_ERROR) {
GL_ALLOC_CALL(gpu->glInterface(), BufferData(target,
(GrGLsizeiptr) size,
data,
fUsage));
if (CHECK_ALLOC_ERROR(gpu->glInterface()) != GR_GL_NO_ERROR) {
GL_CALL(DeleteBuffers(1, &fBufferID)); GL_CALL(DeleteBuffers(1, &fBufferID));
fBufferID = 0; fBufferID = 0;
} else { } else {
@ -182,7 +179,11 @@ void GrGLBuffer::onMap() {
if (!readOnly) { if (!readOnly) {
// Let driver know it can discard the old data // Let driver know it can discard the old data
if (this->glCaps().useBufferDataNullHint() || fGLSizeInBytes != this->size()) { if (this->glCaps().useBufferDataNullHint() || fGLSizeInBytes != this->size()) {
GL_CALL(BufferData(target, this->size(), nullptr, fUsage)); GrGLenum error =
GL_ALLOC_CALL(BufferData(target, this->size(), nullptr, fUsage));
if (error != GR_GL_NO_ERROR) {
return;
}
} }
} }
GL_CALL_RET(fMapPtr, MapBuffer(target, readOnly ? GR_GL_READ_ONLY : GR_GL_WRITE_ONLY)); GL_CALL_RET(fMapPtr, MapBuffer(target, readOnly ? GR_GL_READ_ONLY : GR_GL_WRITE_ONLY));
@ -192,7 +193,10 @@ void GrGLBuffer::onMap() {
GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this); GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this);
// Make sure the GL buffer size agrees with fDesc before mapping. // Make sure the GL buffer size agrees with fDesc before mapping.
if (fGLSizeInBytes != this->size()) { if (fGLSizeInBytes != this->size()) {
GL_CALL(BufferData(target, this->size(), nullptr, fUsage)); GrGLenum error = GL_ALLOC_CALL(BufferData(target, this->size(), nullptr, fUsage));
if (error != GR_GL_NO_ERROR) {
return;
}
} }
GrGLbitfield access; GrGLbitfield access;
if (readOnly) { if (readOnly) {
@ -211,7 +215,10 @@ void GrGLBuffer::onMap() {
GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this); GrGLenum target = this->glGpu()->bindBuffer(fIntendedType, this);
// Make sure the GL buffer size agrees with fDesc before mapping. // Make sure the GL buffer size agrees with fDesc before mapping.
if (fGLSizeInBytes != this->size()) { if (fGLSizeInBytes != this->size()) {
GL_CALL(BufferData(target, this->size(), nullptr, fUsage)); GrGLenum error = GL_ALLOC_CALL(BufferData(target, this->size(), nullptr, fUsage));
if (error != GR_GL_NO_ERROR) {
return;
}
} }
GL_CALL_RET(fMapPtr, MapBufferSubData(target, 0, this->size(), GL_CALL_RET(fMapPtr, MapBufferSubData(target, 0, this->size(),
readOnly ? GR_GL_READ_ONLY : GR_GL_WRITE_ONLY)); readOnly ? GR_GL_READ_ONLY : GR_GL_WRITE_ONLY));
@ -266,7 +273,11 @@ bool GrGLBuffer::onUpdateData(const void* src, size_t srcSizeInBytes) {
if (this->glCaps().useBufferDataNullHint()) { if (this->glCaps().useBufferDataNullHint()) {
if (this->size() == srcSizeInBytes) { if (this->size() == srcSizeInBytes) {
GL_CALL(BufferData(target, (GrGLsizeiptr) srcSizeInBytes, src, fUsage)); GrGLenum error =
GL_ALLOC_CALL(BufferData(target, (GrGLsizeiptr)srcSizeInBytes, src, fUsage));
if (error != GR_GL_NO_ERROR) {
return false;
}
} else { } else {
// Before we call glBufferSubData we give the driver a hint using // Before we call glBufferSubData we give the driver a hint using
// glBufferData with nullptr. This makes the old buffer contents // glBufferData with nullptr. This makes the old buffer contents
@ -275,7 +286,11 @@ bool GrGLBuffer::onUpdateData(const void* src, size_t srcSizeInBytes) {
// assign a different allocation for the new contents to avoid // assign a different allocation for the new contents to avoid
// flushing the gpu past draws consuming the old contents. // flushing the gpu past draws consuming the old contents.
// TODO I think we actually want to try calling bufferData here // TODO I think we actually want to try calling bufferData here
GL_CALL(BufferData(target, this->size(), nullptr, fUsage)); GrGLenum error =
GL_ALLOC_CALL(BufferData(target, (GrGLsizeiptr)this->size(), nullptr, fUsage));
if (error != GR_GL_NO_ERROR) {
return false;
}
GL_CALL(BufferSubData(target, 0, (GrGLsizeiptr) srcSizeInBytes, src)); GL_CALL(BufferSubData(target, 0, (GrGLsizeiptr) srcSizeInBytes, src));
} }
fGLSizeInBytes = this->size(); fGLSizeInBytes = this->size();
@ -283,7 +298,11 @@ bool GrGLBuffer::onUpdateData(const void* src, size_t srcSizeInBytes) {
// Note that we're cheating on the size here. Currently no methods // Note that we're cheating on the size here. Currently no methods
// allow a partial update that preserves contents of non-updated // allow a partial update that preserves contents of non-updated
// portions of the buffer (map() does a glBufferData(..size, nullptr..)) // portions of the buffer (map() does a glBufferData(..size, nullptr..))
GL_CALL(BufferData(target, srcSizeInBytes, src, fUsage)); GrGLenum error =
GL_ALLOC_CALL(BufferData(target, (GrGLsizeiptr)srcSizeInBytes, src, fUsage));
if (error != GR_GL_NO_ERROR) {
return false;
}
fGLSizeInBytes = srcSizeInBytes; fGLSizeInBytes = srcSizeInBytes;
} }
VALIDATE(); VALIDATE();

View File

@ -50,9 +50,9 @@
GR_GL_CALL(this->glInterface(), call); \ GR_GL_CALL(this->glInterface(), call); \
return static_cast<GrGLenum>(GR_GL_NO_ERROR); \ return static_cast<GrGLenum>(GR_GL_NO_ERROR); \
} else { \ } else { \
GrGLClearErr(this->glInterface()); \ this->clearErrorsAndCheckForOOM(); \
GR_GL_CALL_NOERRCHECK(this->glInterface(), call); \ GR_GL_CALL_NOERRCHECK(this->glInterface(), call); \
return GR_GL_GET_ERROR(this->glInterface()); \ return this->getErrorAndCheckForOOM(); \
} \ } \
}() }()
@ -333,7 +333,11 @@ GrGLGpu::GrGLGpu(std::unique_ptr<GrGLContext> ctx, GrContext* context)
, fStencilClearFBOID(0) , fStencilClearFBOID(0)
, fFinishCallbacks(this) { , fFinishCallbacks(this) {
SkASSERT(fGLContext); SkASSERT(fGLContext);
GrGLClearErr(this->glInterface()); // Clear errors so we don't get confused whether we caused an error.
this->clearErrorsAndCheckForOOM();
// Toss out any pre-existing OOM that was hanging around before we got started.
this->checkAndResetOOMed();
fCaps = sk_ref_sp(fGLContext->caps()); fCaps = sk_ref_sp(fGLContext->caps());
fHWTextureUnitBindings.reset(this->numTextureUnits()); fHWTextureUnitBindings.reset(this->numTextureUnits());
@ -3862,6 +3866,9 @@ bool GrGLGpu::onSubmitToGpu(bool syncCpu) {
// See if any previously inserted finish procs are good to go. // See if any previously inserted finish procs are good to go.
fFinishCallbacks.check(); fFinishCallbacks.check();
} }
if (!this->glCaps().skipErrorChecks()) {
this->clearErrorsAndCheckForOOM();
}
return true; return true;
} }
@ -3959,6 +3966,23 @@ void GrGLGpu::checkFinishProcs() {
fFinishCallbacks.check(); fFinishCallbacks.check();
} }
void GrGLGpu::clearErrorsAndCheckForOOM() {
while (this->getErrorAndCheckForOOM() != GR_GL_NO_ERROR) {}
}
GrGLenum GrGLGpu::getErrorAndCheckForOOM() {
#if GR_GL_CHECK_ERROR
if (this->glInterface()->checkAndResetOOMed()) {
this->setOOMed();
}
#endif
GrGLenum error = this->fGLContext->glInterface()->fFunctions.fGetError();
if (error == GR_GL_OUT_OF_MEMORY) {
this->setOOMed();
}
return error;
}
void GrGLGpu::deleteSync(GrGLsync sync) const { void GrGLGpu::deleteSync(GrGLsync sync) const {
if (this->glCaps().fenceType() == GrGLCaps::FenceType::kNVFence) { if (this->glCaps().fenceType() == GrGLCaps::FenceType::kNVFence) {
GrGLuint nvFence = SkToUInt(reinterpret_cast<intptr_t>(sync)); GrGLuint nvFence = SkToUInt(reinterpret_cast<intptr_t>(sync));

View File

@ -169,6 +169,11 @@ public:
void checkFinishProcs() override; void checkFinishProcs() override;
// Calls glGetError() until no errors are reported. Also looks for OOMs.
void clearErrorsAndCheckForOOM();
// Calls glGetError() once and returns the result. Also looks for an OOM.
GrGLenum getErrorAndCheckForOOM();
std::unique_ptr<GrSemaphore> prepareTextureForCrossContextUsage(GrTexture*) override; std::unique_ptr<GrSemaphore> prepareTextureForCrossContextUsage(GrTexture*) override;
void deleteSync(GrGLsync) const; void deleteSync(GrGLsync) const;

View File

@ -19,6 +19,54 @@ GrGLInterface::GrGLInterface() {
fStandard = kNone_GrGLStandard; fStandard = kNone_GrGLStandard;
} }
#if GR_GL_CHECK_ERROR
static const char* get_error_string(GrGLenum err) {
switch (err) {
case GR_GL_NO_ERROR:
return "";
case GR_GL_INVALID_ENUM:
return "Invalid Enum";
case GR_GL_INVALID_VALUE:
return "Invalid Value";
case GR_GL_INVALID_OPERATION:
return "Invalid Operation";
case GR_GL_OUT_OF_MEMORY:
return "Out of Memory";
case GR_GL_CONTEXT_LOST:
return "Context Lost";
}
return "Unknown";
}
GrGLenum GrGLInterface::checkError(const char* location, const char* call) const {
GrGLenum error = fFunctions.fGetError();
if (error != GR_GL_NO_ERROR && !fSuppressErrorLogging) {
SkDebugf("---- glGetError 0x%x(%s)", error, get_error_string(error));
if (location) {
SkDebugf(" at\n\t%s", location);
}
if (call) {
SkDebugf("\n\t\t%s", call);
}
SkDebugf("\n");
if (error == GR_GL_OUT_OF_MEMORY) {
fOOMed = true;
}
}
return error;
}
bool GrGLInterface::checkAndResetOOMed() const {
if (fOOMed) {
fOOMed = false;
return true;
}
return false;
}
void GrGLInterface::suppressErrorLogging() { fSuppressErrorLogging = true; }
#endif
#define RETURN_FALSE_INTERFACE \ #define RETURN_FALSE_INTERFACE \
SkDEBUGF("%s:%d GrGLInterface::validate() failed.\n", __FILE__, __LINE__); \ SkDEBUGF("%s:%d GrGLInterface::validate() failed.\n", __FILE__, __LINE__); \
return false return false

View File

@ -12,46 +12,6 @@
#include "src/gpu/gl/GrGLUtil.h" #include "src/gpu/gl/GrGLUtil.h"
#include <stdio.h> #include <stdio.h>
void GrGLClearErr(const GrGLInterface* gl) {
while (GR_GL_NO_ERROR != gl->fFunctions.fGetError()) {}
}
namespace {
const char *get_error_string(uint32_t err) {
switch (err) {
case GR_GL_NO_ERROR:
return "";
case GR_GL_INVALID_ENUM:
return "Invalid Enum";
case GR_GL_INVALID_VALUE:
return "Invalid Value";
case GR_GL_INVALID_OPERATION:
return "Invalid Operation";
case GR_GL_OUT_OF_MEMORY:
return "Out of Memory";
case GR_GL_CONTEXT_LOST:
return "Context Lost";
}
return "Unknown";
}
}
void GrGLCheckErr(const GrGLInterface* gl,
const char* location,
const char* call) {
uint32_t err = GR_GL_GET_ERROR(gl);
if (GR_GL_NO_ERROR != err) {
SkDebugf("---- glGetError 0x%x(%s)", err, get_error_string(err));
if (location) {
SkDebugf(" at\n\t%s", location);
}
if (call) {
SkDebugf("\n\t\t%s", call);
}
SkDebugf("\n");
}
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#if GR_GL_LOG_CALLS #if GR_GL_LOG_CALLS

View File

@ -257,23 +257,25 @@ void GrGLCheckErr(const GrGLInterface* gl,
const char* location, const char* location,
const char* call); const char* call);
void GrGLClearErr(const GrGLInterface* gl);
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/** /**
* Macros for using GrGLInterface to make GL calls * Macros for using GrGLInterface to make GL calls
*/ */
// internal macro to conditionally call glGetError based on compile-time and // Conditionally checks glGetError based on compile-time and run-time flags.
// run-time flags.
#if GR_GL_CHECK_ERROR #if GR_GL_CHECK_ERROR
extern bool gCheckErrorGL; extern bool gCheckErrorGL;
#define GR_GL_CHECK_ERROR_IMPL(IFACE, X) \ #define GR_GL_CHECK_ERROR_IMPL(IFACE, X) \
if (gCheckErrorGL) \ do { \
GrGLCheckErr(IFACE, GR_FILE_AND_LINE_STR, #X) if (gCheckErrorGL) { \
IFACE->checkError(GR_FILE_AND_LINE_STR, #X); \
} \
} while (false)
#else #else
#define GR_GL_CHECK_ERROR_IMPL(IFACE, X) #define GR_GL_CHECK_ERROR_IMPL(IFACE, X) \
do { \
} while (false)
#endif #endif
// internal macro to conditionally log the gl call using SkDebugf based on // internal macro to conditionally log the gl call using SkDebugf based on
@ -316,9 +318,6 @@ void GrGLClearErr(const GrGLInterface* gl);
GR_GL_LOG_CALLS_IMPL(X); \ GR_GL_LOG_CALLS_IMPL(X); \
} while (false) } while (false)
// call glGetError without doing a redundant error check or logging.
#define GR_GL_GET_ERROR(IFACE) (IFACE)->fFunctions.fGetError()
static constexpr GrGLFormat GrGLFormatFromGLEnum(GrGLenum glFormat) { static constexpr GrGLFormat GrGLFormatFromGLEnum(GrGLenum glFormat) {
switch (glFormat) { switch (glFormat) {
case GR_GL_RGBA8: return GrGLFormat::kRGBA8; case GR_GL_RGBA8: return GrGLFormat::kRGBA8;

View File

@ -275,11 +275,11 @@ sk_sp<GrGLProgram> GrGLProgramBuilder::finalize(const GrGLPrecompiledProgram* pr
if (!reader.isValid()) { if (!reader.isValid()) {
break; break;
} }
GrGLClearErr(this->gpu()->glInterface()); this->gpu()->clearErrorsAndCheckForOOM();
GR_GL_CALL_NOERRCHECK(this->gpu()->glInterface(), GR_GL_CALL_NOERRCHECK(this->gpu()->glInterface(),
ProgramBinary(programID, binaryFormat, ProgramBinary(programID, binaryFormat,
const_cast<void*>(binary), length)); const_cast<void*>(binary), length));
if (GR_GL_GET_ERROR(this->gpu()->glInterface()) == GR_GL_NO_ERROR) { if (this->gpu()->getErrorAndCheckForOOM() == GR_GL_NO_ERROR) {
if (checkLinked) { if (checkLinked) {
cached = this->checkLinkStatus(programID, errorHandler, nullptr, nullptr); cached = this->checkLinkStatus(programID, errorHandler, nullptr, nullptr);
} }

View File

@ -171,6 +171,8 @@ public:
const SkIRect& bounds, bool forSecondaryCB); const SkIRect& bounds, bool forSecondaryCB);
void endRenderPass(GrRenderTarget* target, GrSurfaceOrigin origin, const SkIRect& bounds); void endRenderPass(GrRenderTarget* target, GrSurfaceOrigin origin, const SkIRect& bounds);
using GrGpu::setOOMed;
private: private:
enum SyncQueue { enum SyncQueue {
kForce_SyncQueue, kForce_SyncQueue,

View File

@ -21,25 +21,31 @@ class GrVkGpu;
// makes a Vk call on the interface // makes a Vk call on the interface
#define GR_VK_CALL(IFACE, X) (IFACE)->fFunctions.f##X #define GR_VK_CALL(IFACE, X) (IFACE)->fFunctions.f##X
#define GR_VK_CALL_RESULT(GPU, RESULT, X) \ #define GR_VK_CALL_RESULT(GPU, RESULT, X) \
do { \ do { \
(RESULT) = GR_VK_CALL(GPU->vkInterface(), X); \ (RESULT) = GR_VK_CALL(GPU->vkInterface(), X); \
SkASSERT(VK_SUCCESS == RESULT || VK_ERROR_DEVICE_LOST == RESULT); \ SkASSERT(VK_SUCCESS == RESULT || VK_ERROR_DEVICE_LOST == RESULT); \
if (RESULT != VK_SUCCESS && !GPU->isDeviceLost()) { \ if (RESULT != VK_SUCCESS && !GPU->isDeviceLost()) { \
SkDebugf("Failed vulkan call. Error: %d\n", RESULT); \ SkDebugf("Failed vulkan call. Error: %d," #X "\n", RESULT); \
} \ } \
if (VK_ERROR_DEVICE_LOST == RESULT) { \ if (RESULT == VK_ERROR_DEVICE_LOST) { \
GPU->setDeviceLost(); \ GPU->setDeviceLost(); \
} \ } else if (RESULT == VK_ERROR_OUT_OF_HOST_MEMORY || \
} while(false) RESULT == VK_ERROR_OUT_OF_DEVICE_MEMORY) { \
GPU->setOOMed(); \
} \
} while (false)
#define GR_VK_CALL_RESULT_NOCHECK(GPU, RESULT, X) \ #define GR_VK_CALL_RESULT_NOCHECK(GPU, RESULT, X) \
do { \ do { \
(RESULT) = GR_VK_CALL(GPU->vkInterface(), X); \ (RESULT) = GR_VK_CALL(GPU->vkInterface(), X); \
if (VK_ERROR_DEVICE_LOST == RESULT) { \ if (RESULT == VK_ERROR_DEVICE_LOST) { \
GPU->setDeviceLost(); \ GPU->setDeviceLost(); \
} \ } else if (RESULT == VK_ERROR_OUT_OF_HOST_MEMORY || \
} while(false) RESULT == VK_ERROR_OUT_OF_DEVICE_MEMORY) { \
GPU->setOOMed(); \
} \
} while (false)
// same as GR_VK_CALL but checks for success // same as GR_VK_CALL but checks for success
#define GR_VK_CALL_ERRCHECK(GPU, X) \ #define GR_VK_CALL_ERRCHECK(GPU, X) \

53
tests/GrContextOOM.cpp Normal file
View File

@ -0,0 +1,53 @@
/*
* Copyright 2020 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkCanvas.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPaint.h"
#include "include/core/SkRect.h"
#include "include/core/SkSurface.h"
#include "include/gpu/GrContext.h"
#include "tests/Test.h"
DEF_GPUTEST(GrContext_oomed, reporter, originalOptions) {
GrContextOptions options = originalOptions;
options.fRandomGLOOM = true;
options.fSkipGLErrorChecks = GrContextOptions::Enable::kNo;
sk_gpu_test::GrContextFactory factory(options);
for (int ct = 0; ct < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++ct) {
auto contextType = static_cast<sk_gpu_test::GrContextFactory::ContextType>(ct);
GrContext* context = factory.get(contextType);
if (!context) {
continue;
}
if (context->backend() != GrBackendApi::kOpenGL) {
continue;
}
auto info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
for (int run = 0; run < 20; ++run) {
bool oomed = false;
for (int i = 0; i < 500; ++i) {
// Make sure we're actually making a significant number of GL calls and not just
// issuing a small number calls by reusing scratch resources created in a previous
// iteration.
context->freeGpuResources();
auto surf =
SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 1, nullptr);
SkPaint paint;
surf->getCanvas()->drawRect(SkRect::MakeLTRB(100, 100, 2000, 2000), paint);
surf->flushAndSubmit();
if ((oomed = context->oomed())) {
REPORTER_ASSERT(reporter, !context->oomed(), "oomed() wasn't cleared");
break;
}
}
if (!oomed) {
ERRORF(reporter, "Did not OOM on %dth run.", run);
}
}
}
}

View File

@ -217,7 +217,7 @@ bool EGLTestHelper::init(skiatest::Reporter* reporter) {
} }
bool EGLTestHelper::importHardwareBuffer(skiatest::Reporter* reporter, AHardwareBuffer* buffer) { bool EGLTestHelper::importHardwareBuffer(skiatest::Reporter* reporter, AHardwareBuffer* buffer) {
GrGLClearErr(fGLCtx->gl()); while (fGLCtx->gl()->fFunctions.fGetError() != GR_GL_NO_ERROR) {}
EGLClientBuffer eglClientBuffer = fEGLGetNativeClientBufferANDROID(buffer); EGLClientBuffer eglClientBuffer = fEGLGetNativeClientBufferANDROID(buffer);
EGLint eglAttribs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGLint eglAttribs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
@ -237,15 +237,14 @@ bool EGLTestHelper::importHardwareBuffer(skiatest::Reporter* reporter, AHardware
return false; return false;
} }
GR_GL_CALL_NOERRCHECK(fGLCtx->gl(), BindTexture(GR_GL_TEXTURE_2D, fTexID)); GR_GL_CALL_NOERRCHECK(fGLCtx->gl(), BindTexture(GR_GL_TEXTURE_2D, fTexID));
if (GR_GL_GET_ERROR(fGLCtx->gl()) != GR_GL_NO_ERROR) { if (fGLCtx->gl()->fFunctions.fGetError() != GR_GL_NO_ERROR) {
ERRORF(reporter, "Failed to bind GL Texture"); ERRORF(reporter, "Failed to bind GL Texture");
return false; return false;
} }
fEGLImageTargetTexture2DOES(GL_TEXTURE_2D, fImage); fEGLImageTargetTexture2DOES(GL_TEXTURE_2D, fImage);
GLenum status = GL_NO_ERROR; if (GrGLenum error = fGLCtx->gl()->fFunctions.fGetError(); error != GR_GL_NO_ERROR) {
if ((status = glGetError()) != GL_NO_ERROR) { ERRORF(reporter, "EGLImageTargetTexture2DOES failed (%#x)", (int) error);
ERRORF(reporter, "EGLImageTargetTexture2DOES failed (%#x)", (int) status);
return false; return false;
} }

View File

@ -375,7 +375,7 @@ GrEGLImage ANGLEGLContext::texture2DToEGLImage(GrGLuint texID) const {
void ANGLEGLContext::destroyEGLImage(GrEGLImage image) const { fDestroyImage(fDisplay, image); } void ANGLEGLContext::destroyEGLImage(GrEGLImage image) const { fDestroyImage(fDisplay, image); }
GrGLuint ANGLEGLContext::eglImageToExternalTexture(GrEGLImage image) const { GrGLuint ANGLEGLContext::eglImageToExternalTexture(GrEGLImage image) const {
GrGLClearErr(this->gl()); while (this->gl()->fFunctions.fGetError() != GR_GL_NO_ERROR) {}
if (!this->gl()->hasExtension("GL_OES_EGL_image_external")) { if (!this->gl()->hasExtension("GL_OES_EGL_image_external")) {
return 0; return 0;
} }
@ -391,12 +391,12 @@ GrGLuint ANGLEGLContext::eglImageToExternalTexture(GrEGLImage image) const {
return 0; return 0;
} }
GR_GL_CALL(this->gl(), BindTexture(GR_GL_TEXTURE_EXTERNAL, texID)); GR_GL_CALL(this->gl(), BindTexture(GR_GL_TEXTURE_EXTERNAL, texID));
if (GR_GL_GET_ERROR(this->gl()) != GR_GL_NO_ERROR) { if (this->gl()->fFunctions.fGetError() != GR_GL_NO_ERROR) {
GR_GL_CALL(this->gl(), DeleteTextures(1, &texID)); GR_GL_CALL(this->gl(), DeleteTextures(1, &texID));
return 0; return 0;
} }
glEGLImageTargetTexture2D(GR_GL_TEXTURE_EXTERNAL, image); glEGLImageTargetTexture2D(GR_GL_TEXTURE_EXTERNAL, image);
if (GR_GL_GET_ERROR(this->gl()) != GR_GL_NO_ERROR) { if (this->gl()->fFunctions.fGetError() != GR_GL_NO_ERROR) {
GR_GL_CALL(this->gl(), DeleteTextures(1, &texID)); GR_GL_CALL(this->gl(), DeleteTextures(1, &texID));
return 0; return 0;
} }

View File

@ -375,7 +375,7 @@ void EGLGLTestContext::destroyEGLImage(GrEGLImage image) const {
GrGLuint EGLGLTestContext::eglImageToExternalTexture(GrEGLImage image) const { GrGLuint EGLGLTestContext::eglImageToExternalTexture(GrEGLImage image) const {
#ifdef SK_GL #ifdef SK_GL
GrGLClearErr(this->gl()); while (this->gl()->fFunctions.fGetError() != GR_GL_NO_ERROR) {}
if (!this->gl()->hasExtension("GL_OES_EGL_image_external")) { if (!this->gl()->hasExtension("GL_OES_EGL_image_external")) {
return 0; return 0;
} }
@ -392,12 +392,12 @@ GrGLuint EGLGLTestContext::eglImageToExternalTexture(GrEGLImage image) const {
return 0; return 0;
} }
GR_GL_CALL_NOERRCHECK(this->gl(), BindTexture(GR_GL_TEXTURE_EXTERNAL, texID)); GR_GL_CALL_NOERRCHECK(this->gl(), BindTexture(GR_GL_TEXTURE_EXTERNAL, texID));
if (GR_GL_GET_ERROR(this->gl()) != GR_GL_NO_ERROR) { if (this->gl()->fFunctions.fGetError() != GR_GL_NO_ERROR) {
GR_GL_CALL(this->gl(), DeleteTextures(1, &texID)); GR_GL_CALL(this->gl(), DeleteTextures(1, &texID));
return 0; return 0;
} }
glEGLImageTargetTexture2D(GR_GL_TEXTURE_EXTERNAL, image); glEGLImageTargetTexture2D(GR_GL_TEXTURE_EXTERNAL, image);
if (GR_GL_GET_ERROR(this->gl()) != GR_GL_NO_ERROR) { if (this->gl()->fFunctions.fGetError() != GR_GL_NO_ERROR) {
GR_GL_CALL(this->gl(), DeleteTextures(1, &texID)); GR_GL_CALL(this->gl(), DeleteTextures(1, &texID));
return 0; return 0;
} }

View File

@ -234,6 +234,54 @@ GrGLInterface::GrGLInterface() {
fStandard = kNone_GrGLStandard; fStandard = kNone_GrGLStandard;
} }
#if GR_GL_CHECK_ERROR
static const char* get_error_string(GrGLenum err) {
switch (err) {
case GR_GL_NO_ERROR:
return "";
case GR_GL_INVALID_ENUM:
return "Invalid Enum";
case GR_GL_INVALID_VALUE:
return "Invalid Value";
case GR_GL_INVALID_OPERATION:
return "Invalid Operation";
case GR_GL_OUT_OF_MEMORY:
return "Out of Memory";
case GR_GL_CONTEXT_LOST:
return "Context Lost";
}
return "Unknown";
}
GrGLenum GrGLInterface::checkError(const char* location, const char* call) const {
GrGLenum error = fFunctions.fGetError();
if (error != GR_GL_NO_ERROR && !fSuppressErrorLogging) {
SkDebugf("---- glGetError 0x%x(%s)", error, get_error_string(error));
if (location) {
SkDebugf(" at\n\t%s", location);
}
if (call) {
SkDebugf("\n\t\t%s", call);
}
SkDebugf("\n");
if (error == GR_GL_OUT_OF_MEMORY) {
fOOMed = true;
}
}
return error;
}
bool GrGLInterface::checkAndResetOOMed() const {
if (fOOMed) {
fOOMed = false;
return true;
}
return false;
}
void GrGLInterface::suppressErrorLogging() { fSuppressErrorLogging = true; }
#endif
#define RETURN_FALSE_INTERFACE \ #define RETURN_FALSE_INTERFACE \
SkDEBUGF("%s:%d GrGLInterface::validate() failed.\n", __FILE__, __LINE__); \ SkDEBUGF("%s:%d GrGLInterface::validate() failed.\n", __FILE__, __LINE__); \
return false return false

View File

@ -317,3 +317,11 @@
fun:_Z12GrClearImageRK11GrImageInfoPvm8SkRGBA4fIL11SkAlphaType3EE fun:_Z12GrClearImageRK11GrImageInfoPvm8SkRGBA4fIL11SkAlphaType3EE
... ...
} }
{
make_get_error_with_random_oom
Memcheck:Leak
match-leak-kinds: definite
...
fun:_Z30make_get_error_with_random_oom12GrGLFunctionIFjvEE
...
}