| // |
| // Copyright 2015 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. |
| // |
| |
| // RendererGL.cpp: Implements the class methods for RendererGL. |
| |
| #include "libANGLE/renderer/gl/RendererGL.h" |
| |
| #include <EGL/eglext.h> |
| |
| #include "common/debug.h" |
| #include "libANGLE/AttributeMap.h" |
| #include "libANGLE/Context.h" |
| #include "libANGLE/Display.h" |
| #include "libANGLE/Path.h" |
| #include "libANGLE/State.h" |
| #include "libANGLE/Surface.h" |
| #include "libANGLE/renderer/gl/BlitGL.h" |
| #include "libANGLE/renderer/gl/BufferGL.h" |
| #include "libANGLE/renderer/gl/ClearMultiviewGL.h" |
| #include "libANGLE/renderer/gl/CompilerGL.h" |
| #include "libANGLE/renderer/gl/ContextGL.h" |
| #include "libANGLE/renderer/gl/DisplayGL.h" |
| #include "libANGLE/renderer/gl/FenceNVGL.h" |
| #include "libANGLE/renderer/gl/FramebufferGL.h" |
| #include "libANGLE/renderer/gl/FunctionsGL.h" |
| #include "libANGLE/renderer/gl/PathGL.h" |
| #include "libANGLE/renderer/gl/ProgramGL.h" |
| #include "libANGLE/renderer/gl/QueryGL.h" |
| #include "libANGLE/renderer/gl/RenderbufferGL.h" |
| #include "libANGLE/renderer/gl/SamplerGL.h" |
| #include "libANGLE/renderer/gl/ShaderGL.h" |
| #include "libANGLE/renderer/gl/StateManagerGL.h" |
| #include "libANGLE/renderer/gl/SurfaceGL.h" |
| #include "libANGLE/renderer/gl/SyncGL.h" |
| #include "libANGLE/renderer/gl/TextureGL.h" |
| #include "libANGLE/renderer/gl/TransformFeedbackGL.h" |
| #include "libANGLE/renderer/gl/VertexArrayGL.h" |
| #include "libANGLE/renderer/gl/renderergl_utils.h" |
| #include "libANGLE/renderer/renderer_utils.h" |
| |
| namespace |
| { |
| |
| std::vector<GLuint> GatherPaths(const std::vector<gl::Path *> &paths) |
| { |
| std::vector<GLuint> ret; |
| ret.reserve(paths.size()); |
| |
| for (const auto *p : paths) |
| { |
| const auto *pathObj = rx::GetImplAs<rx::PathGL>(p); |
| ret.push_back(pathObj->getPathID()); |
| } |
| return ret; |
| } |
| |
| void SetMaxShaderCompilerThreads(const rx::FunctionsGL *functions, GLuint count) |
| { |
| if (functions->maxShaderCompilerThreadsKHR != nullptr) |
| { |
| functions->maxShaderCompilerThreadsKHR(count); |
| } |
| else |
| { |
| ASSERT(functions->maxShaderCompilerThreadsARB != nullptr); |
| functions->maxShaderCompilerThreadsARB(count); |
| } |
| } |
| |
| #if defined(ANGLE_PLATFORM_ANDROID) |
| const char *kIgnoredErrors[] = { |
| // Wrong error message on Android Q Pixel 2. http://anglebug.com/3491 |
| "FreeAllocationOnTimestamp - Reference to buffer created from " |
| "different context without a share list. Application failed to pass " |
| "share_context to eglCreateContext. Results are undefined.", |
| }; |
| #endif // defined(ANGLE_PLATFORM_ANDROID) |
| } // namespace |
| |
| static void INTERNAL_GL_APIENTRY LogGLDebugMessage(GLenum source, |
| GLenum type, |
| GLuint id, |
| GLenum severity, |
| GLsizei length, |
| const GLchar *message, |
| const void *userParam) |
| { |
| std::string sourceText = gl::GetDebugMessageSourceString(source); |
| std::string typeText = gl::GetDebugMessageTypeString(type); |
| std::string severityText = gl::GetDebugMessageSeverityString(severity); |
| |
| #if defined(ANGLE_PLATFORM_ANDROID) |
| if (type == GL_DEBUG_TYPE_ERROR) |
| { |
| for (const char *&err : kIgnoredErrors) |
| { |
| if (strncmp(err, message, length) == 0) |
| { |
| // There is only one ignored message right now and it is quite spammy, around 3MB |
| // for a complete end2end tests run, so don't print it even as a warning. |
| return; |
| } |
| } |
| } |
| #endif // defined(ANGLE_PLATFORM_ANDROID) |
| |
| if (type == GL_DEBUG_TYPE_ERROR) |
| { |
| ERR() << std::endl |
| << "\tSource: " << sourceText << std::endl |
| << "\tType: " << typeText << std::endl |
| << "\tID: " << gl::FmtHex(id) << std::endl |
| << "\tSeverity: " << severityText << std::endl |
| << "\tMessage: " << message; |
| } |
| else if (type != GL_DEBUG_TYPE_PERFORMANCE) |
| { |
| // Don't print performance warnings. They tend to be very spammy in the dEQP test suite and |
| // there is very little we can do about them. |
| |
| // TODO(ynovikov): filter into WARN and INFO if INFO is ever implemented |
| WARN() << std::endl |
| << "\tSource: " << sourceText << std::endl |
| << "\tType: " << typeText << std::endl |
| << "\tID: " << gl::FmtHex(id) << std::endl |
| << "\tSeverity: " << severityText << std::endl |
| << "\tMessage: " << message; |
| } |
| } |
| |
| namespace rx |
| { |
| |
| RendererGL::RendererGL(std::unique_ptr<FunctionsGL> functions, |
| const egl::AttributeMap &attribMap, |
| DisplayGL *display) |
| : mMaxSupportedESVersion(0, 0), |
| mFunctions(std::move(functions)), |
| mStateManager(nullptr), |
| mBlitter(nullptr), |
| mMultiviewClearer(nullptr), |
| mUseDebugOutput(false), |
| mCapsInitialized(false), |
| mMultiviewImplementationType(MultiviewImplementationTypeGL::UNSPECIFIED), |
| mNativeParallelCompileEnabled(false), |
| mNeedsFlushBeforeDeleteTextures(false) |
| { |
| ASSERT(mFunctions); |
| if (!display->getState().featuresAllDisabled) |
| { |
| nativegl_gl::InitializeFeatures(mFunctions.get(), &mFeatures); |
| } |
| ApplyFeatureOverrides(&mFeatures, display->getState()); |
| mStateManager = |
| new StateManagerGL(mFunctions.get(), getNativeCaps(), getNativeExtensions(), mFeatures); |
| mBlitter = new BlitGL(mFunctions.get(), mFeatures, mStateManager); |
| mMultiviewClearer = new ClearMultiviewGL(mFunctions.get(), mStateManager); |
| |
| bool hasDebugOutput = mFunctions->isAtLeastGL(gl::Version(4, 3)) || |
| mFunctions->hasGLExtension("GL_KHR_debug") || |
| mFunctions->isAtLeastGLES(gl::Version(3, 2)) || |
| mFunctions->hasGLESExtension("GL_KHR_debug"); |
| |
| mUseDebugOutput = hasDebugOutput && ShouldUseDebugLayers(attribMap); |
| |
| if (mUseDebugOutput) |
| { |
| mFunctions->enable(GL_DEBUG_OUTPUT); |
| mFunctions->enable(GL_DEBUG_OUTPUT_SYNCHRONOUS); |
| mFunctions->debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, |
| nullptr, GL_TRUE); |
| mFunctions->debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, |
| nullptr, GL_TRUE); |
| mFunctions->debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW, 0, |
| nullptr, GL_FALSE); |
| mFunctions->debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, |
| 0, nullptr, GL_FALSE); |
| mFunctions->debugMessageCallback(&LogGLDebugMessage, nullptr); |
| } |
| |
| if (mFeatures.initializeCurrentVertexAttributes.enabled) |
| { |
| GLint maxVertexAttribs = 0; |
| mFunctions->getIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs); |
| |
| for (GLint i = 0; i < maxVertexAttribs; ++i) |
| { |
| mFunctions->vertexAttrib4f(i, 0.0f, 0.0f, 0.0f, 1.0f); |
| } |
| } |
| |
| if (hasNativeParallelCompile() && !mNativeParallelCompileEnabled) |
| { |
| SetMaxShaderCompilerThreads(mFunctions.get(), 0xffffffff); |
| mNativeParallelCompileEnabled = true; |
| } |
| } |
| |
| RendererGL::~RendererGL() |
| { |
| SafeDelete(mBlitter); |
| SafeDelete(mMultiviewClearer); |
| SafeDelete(mStateManager); |
| |
| std::lock_guard<std::mutex> lock(mWorkerMutex); |
| |
| ASSERT(mCurrentWorkerContexts.empty()); |
| mWorkerContextPool.clear(); |
| } |
| |
| angle::Result RendererGL::flush() |
| { |
| mFunctions->flush(); |
| mNeedsFlushBeforeDeleteTextures = false; |
| return angle::Result::Continue; |
| } |
| |
| angle::Result RendererGL::finish() |
| { |
| if (mFeatures.finishDoesNotCauseQueriesToBeAvailable.enabled && mUseDebugOutput) |
| { |
| mFunctions->enable(GL_DEBUG_OUTPUT_SYNCHRONOUS); |
| } |
| |
| mFunctions->finish(); |
| mNeedsFlushBeforeDeleteTextures = false; |
| |
| if (mFeatures.finishDoesNotCauseQueriesToBeAvailable.enabled && mUseDebugOutput) |
| { |
| mFunctions->disable(GL_DEBUG_OUTPUT_SYNCHRONOUS); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| void RendererGL::stencilFillPath(const gl::State &state, |
| const gl::Path *path, |
| GLenum fillMode, |
| GLuint mask) |
| { |
| const auto *pathObj = GetImplAs<PathGL>(path); |
| |
| mFunctions->stencilFillPathNV(pathObj->getPathID(), fillMode, mask); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| |
| void RendererGL::stencilStrokePath(const gl::State &state, |
| const gl::Path *path, |
| GLint reference, |
| GLuint mask) |
| { |
| const auto *pathObj = GetImplAs<PathGL>(path); |
| |
| mFunctions->stencilStrokePathNV(pathObj->getPathID(), reference, mask); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| |
| void RendererGL::coverFillPath(const gl::State &state, const gl::Path *path, GLenum coverMode) |
| { |
| |
| const auto *pathObj = GetImplAs<PathGL>(path); |
| mFunctions->coverFillPathNV(pathObj->getPathID(), coverMode); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| |
| void RendererGL::coverStrokePath(const gl::State &state, const gl::Path *path, GLenum coverMode) |
| { |
| const auto *pathObj = GetImplAs<PathGL>(path); |
| mFunctions->coverStrokePathNV(pathObj->getPathID(), coverMode); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| |
| void RendererGL::stencilThenCoverFillPath(const gl::State &state, |
| const gl::Path *path, |
| GLenum fillMode, |
| GLuint mask, |
| GLenum coverMode) |
| { |
| |
| const auto *pathObj = GetImplAs<PathGL>(path); |
| mFunctions->stencilThenCoverFillPathNV(pathObj->getPathID(), fillMode, mask, coverMode); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| |
| void RendererGL::stencilThenCoverStrokePath(const gl::State &state, |
| const gl::Path *path, |
| GLint reference, |
| GLuint mask, |
| GLenum coverMode) |
| { |
| |
| const auto *pathObj = GetImplAs<PathGL>(path); |
| mFunctions->stencilThenCoverStrokePathNV(pathObj->getPathID(), reference, mask, coverMode); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| |
| void RendererGL::coverFillPathInstanced(const gl::State &state, |
| const std::vector<gl::Path *> &paths, |
| GLenum coverMode, |
| GLenum transformType, |
| const GLfloat *transformValues) |
| { |
| const auto &pathObjs = GatherPaths(paths); |
| |
| mFunctions->coverFillPathInstancedNV(static_cast<GLsizei>(pathObjs.size()), GL_UNSIGNED_INT, |
| &pathObjs[0], 0, coverMode, transformType, |
| transformValues); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| void RendererGL::coverStrokePathInstanced(const gl::State &state, |
| const std::vector<gl::Path *> &paths, |
| GLenum coverMode, |
| GLenum transformType, |
| const GLfloat *transformValues) |
| { |
| const auto &pathObjs = GatherPaths(paths); |
| |
| mFunctions->coverStrokePathInstancedNV(static_cast<GLsizei>(pathObjs.size()), GL_UNSIGNED_INT, |
| &pathObjs[0], 0, coverMode, transformType, |
| transformValues); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| void RendererGL::stencilFillPathInstanced(const gl::State &state, |
| const std::vector<gl::Path *> &paths, |
| GLenum fillMode, |
| GLuint mask, |
| GLenum transformType, |
| const GLfloat *transformValues) |
| { |
| const auto &pathObjs = GatherPaths(paths); |
| |
| mFunctions->stencilFillPathInstancedNV(static_cast<GLsizei>(pathObjs.size()), GL_UNSIGNED_INT, |
| &pathObjs[0], 0, fillMode, mask, transformType, |
| transformValues); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| void RendererGL::stencilStrokePathInstanced(const gl::State &state, |
| const std::vector<gl::Path *> &paths, |
| GLint reference, |
| GLuint mask, |
| GLenum transformType, |
| const GLfloat *transformValues) |
| { |
| const auto &pathObjs = GatherPaths(paths); |
| |
| mFunctions->stencilStrokePathInstancedNV(static_cast<GLsizei>(pathObjs.size()), GL_UNSIGNED_INT, |
| &pathObjs[0], 0, reference, mask, transformType, |
| transformValues); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| |
| void RendererGL::stencilThenCoverFillPathInstanced(const gl::State &state, |
| const std::vector<gl::Path *> &paths, |
| GLenum coverMode, |
| GLenum fillMode, |
| GLuint mask, |
| GLenum transformType, |
| const GLfloat *transformValues) |
| { |
| const auto &pathObjs = GatherPaths(paths); |
| |
| mFunctions->stencilThenCoverFillPathInstancedNV( |
| static_cast<GLsizei>(pathObjs.size()), GL_UNSIGNED_INT, &pathObjs[0], 0, fillMode, mask, |
| coverMode, transformType, transformValues); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| void RendererGL::stencilThenCoverStrokePathInstanced(const gl::State &state, |
| const std::vector<gl::Path *> &paths, |
| GLenum coverMode, |
| GLint reference, |
| GLuint mask, |
| GLenum transformType, |
| const GLfloat *transformValues) |
| { |
| const auto &pathObjs = GatherPaths(paths); |
| |
| mFunctions->stencilThenCoverStrokePathInstancedNV( |
| static_cast<GLsizei>(pathObjs.size()), GL_UNSIGNED_INT, &pathObjs[0], 0, reference, mask, |
| coverMode, transformType, transformValues); |
| |
| ASSERT(mFunctions->getError() == GL_NO_ERROR); |
| } |
| |
| gl::GraphicsResetStatus RendererGL::getResetStatus() |
| { |
| return gl::FromGLenum<gl::GraphicsResetStatus>(mFunctions->getGraphicsResetStatus()); |
| } |
| |
| void RendererGL::insertEventMarker(GLsizei length, const char *marker) {} |
| |
| void RendererGL::pushGroupMarker(GLsizei length, const char *marker) {} |
| |
| void RendererGL::popGroupMarker() {} |
| |
| void RendererGL::pushDebugGroup(GLenum source, GLuint id, const std::string &message) {} |
| |
| void RendererGL::popDebugGroup() {} |
| |
| std::string RendererGL::getVendorString() const |
| { |
| return std::string(reinterpret_cast<const char *>(mFunctions->getString(GL_VENDOR))); |
| } |
| |
| std::string RendererGL::getRendererDescription() const |
| { |
| std::string nativeVendorString( |
| reinterpret_cast<const char *>(mFunctions->getString(GL_VENDOR))); |
| std::string nativeRendererString( |
| reinterpret_cast<const char *>(mFunctions->getString(GL_RENDERER))); |
| |
| std::ostringstream rendererString; |
| rendererString << nativeVendorString << ", " << nativeRendererString << ", OpenGL"; |
| if (mFunctions->standard == STANDARD_GL_ES) |
| { |
| rendererString << " ES"; |
| } |
| rendererString << " " << mFunctions->version.major << "." << mFunctions->version.minor; |
| if (mFunctions->standard == STANDARD_GL_DESKTOP) |
| { |
| // Some drivers (NVIDIA) use a profile mask of 0 when in compatibility profile. |
| if ((mFunctions->profile & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0 || |
| (mFunctions->isAtLeastGL(gl::Version(3, 2)) && mFunctions->profile == 0)) |
| { |
| rendererString << " compatibility"; |
| } |
| else if ((mFunctions->profile & GL_CONTEXT_CORE_PROFILE_BIT) != 0) |
| { |
| rendererString << " core"; |
| } |
| } |
| |
| return rendererString.str(); |
| } |
| |
| const gl::Version &RendererGL::getMaxSupportedESVersion() const |
| { |
| // Force generation of caps |
| getNativeCaps(); |
| |
| return mMaxSupportedESVersion; |
| } |
| |
| void RendererGL::generateCaps(gl::Caps *outCaps, |
| gl::TextureCapsMap *outTextureCaps, |
| gl::Extensions *outExtensions, |
| gl::Limitations * /* outLimitations */) const |
| { |
| nativegl_gl::GenerateCaps(mFunctions.get(), mFeatures, outCaps, outTextureCaps, outExtensions, |
| &mMaxSupportedESVersion, &mMultiviewImplementationType); |
| } |
| |
| GLint RendererGL::getGPUDisjoint() |
| { |
| // TODO(ewell): On GLES backends we should find a way to reliably query disjoint events |
| return 0; |
| } |
| |
| GLint64 RendererGL::getTimestamp() |
| { |
| GLint64 result = 0; |
| mFunctions->getInteger64v(GL_TIMESTAMP, &result); |
| return result; |
| } |
| |
| void RendererGL::ensureCapsInitialized() const |
| { |
| if (!mCapsInitialized) |
| { |
| generateCaps(&mNativeCaps, &mNativeTextureCaps, &mNativeExtensions, &mNativeLimitations); |
| mCapsInitialized = true; |
| } |
| } |
| |
| const gl::Caps &RendererGL::getNativeCaps() const |
| { |
| ensureCapsInitialized(); |
| return mNativeCaps; |
| } |
| |
| const gl::TextureCapsMap &RendererGL::getNativeTextureCaps() const |
| { |
| ensureCapsInitialized(); |
| return mNativeTextureCaps; |
| } |
| |
| const gl::Extensions &RendererGL::getNativeExtensions() const |
| { |
| ensureCapsInitialized(); |
| return mNativeExtensions; |
| } |
| |
| const gl::Limitations &RendererGL::getNativeLimitations() const |
| { |
| ensureCapsInitialized(); |
| return mNativeLimitations; |
| } |
| |
| MultiviewImplementationTypeGL RendererGL::getMultiviewImplementationType() const |
| { |
| ensureCapsInitialized(); |
| return mMultiviewImplementationType; |
| } |
| |
| void RendererGL::initializeFrontendFeatures(angle::FrontendFeatures *features) const |
| { |
| ensureCapsInitialized(); |
| nativegl_gl::InitializeFrontendFeatures(mFunctions.get(), features); |
| } |
| |
| angle::Result RendererGL::dispatchCompute(const gl::Context *context, |
| GLuint numGroupsX, |
| GLuint numGroupsY, |
| GLuint numGroupsZ) |
| { |
| mFunctions->dispatchCompute(numGroupsX, numGroupsY, numGroupsZ); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result RendererGL::dispatchComputeIndirect(const gl::Context *context, GLintptr indirect) |
| { |
| mFunctions->dispatchComputeIndirect(indirect); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result RendererGL::memoryBarrier(GLbitfield barriers) |
| { |
| mFunctions->memoryBarrier(barriers); |
| return angle::Result::Continue; |
| } |
| angle::Result RendererGL::memoryBarrierByRegion(GLbitfield barriers) |
| { |
| mFunctions->memoryBarrierByRegion(barriers); |
| return angle::Result::Continue; |
| } |
| |
| bool RendererGL::bindWorkerContext(std::string *infoLog) |
| { |
| if (mFeatures.disableWorkerContexts.enabled) |
| { |
| return false; |
| } |
| |
| std::thread::id threadID = std::this_thread::get_id(); |
| std::lock_guard<std::mutex> lock(mWorkerMutex); |
| std::unique_ptr<WorkerContext> workerContext; |
| if (!mWorkerContextPool.empty()) |
| { |
| auto it = mWorkerContextPool.begin(); |
| workerContext = std::move(*it); |
| mWorkerContextPool.erase(it); |
| } |
| else |
| { |
| WorkerContext *newContext = createWorkerContext(infoLog); |
| if (newContext == nullptr) |
| { |
| return false; |
| } |
| workerContext.reset(newContext); |
| } |
| |
| if (!workerContext->makeCurrent()) |
| { |
| mWorkerContextPool.push_back(std::move(workerContext)); |
| return false; |
| } |
| mCurrentWorkerContexts[threadID] = std::move(workerContext); |
| return true; |
| } |
| |
| void RendererGL::unbindWorkerContext() |
| { |
| std::thread::id threadID = std::this_thread::get_id(); |
| std::lock_guard<std::mutex> lock(mWorkerMutex); |
| |
| auto it = mCurrentWorkerContexts.find(threadID); |
| ASSERT(it != mCurrentWorkerContexts.end()); |
| (*it).second->unmakeCurrent(); |
| mWorkerContextPool.push_back(std::move((*it).second)); |
| mCurrentWorkerContexts.erase(it); |
| } |
| |
| unsigned int RendererGL::getMaxWorkerContexts() |
| { |
| // No more than 16 worker contexts. |
| return std::min(16u, std::thread::hardware_concurrency()); |
| } |
| |
| bool RendererGL::hasNativeParallelCompile() |
| { |
| return mFunctions->maxShaderCompilerThreadsKHR != nullptr || |
| mFunctions->maxShaderCompilerThreadsARB != nullptr; |
| } |
| |
| void RendererGL::setMaxShaderCompilerThreads(GLuint count) |
| { |
| if (hasNativeParallelCompile()) |
| { |
| SetMaxShaderCompilerThreads(mFunctions.get(), count); |
| } |
| } |
| |
| void RendererGL::setNeedsFlushBeforeDeleteTextures() |
| { |
| mNeedsFlushBeforeDeleteTextures = true; |
| } |
| |
| void RendererGL::flushIfNecessaryBeforeDeleteTextures() |
| { |
| if (mNeedsFlushBeforeDeleteTextures) |
| { |
| (void)flush(); |
| } |
| } |
| |
| ScopedWorkerContextGL::ScopedWorkerContextGL(RendererGL *renderer, std::string *infoLog) |
| : mRenderer(renderer) |
| { |
| mValid = mRenderer->bindWorkerContext(infoLog); |
| } |
| |
| ScopedWorkerContextGL::~ScopedWorkerContextGL() |
| { |
| if (mValid) |
| { |
| mRenderer->unbindWorkerContext(); |
| } |
| } |
| |
| bool ScopedWorkerContextGL::operator()() const |
| { |
| return mValid; |
| } |
| |
| } // namespace rx |