forked from mirrors/gecko-dev
		
	 9130f40b1b
			
		
	
	
		9130f40b1b
		
	
	
	
	
		
			
			MozReview-Commit-ID: 3hOIgDuvRNd --HG-- extra : rebase_source : cdc9aa85aa9054b0ffb5eca3112bdc68a60fa187
		
			
				
	
	
		
			1052 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1052 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "WebGLContext.h"
 | |
| 
 | |
| #include "GeckoProfiler.h"
 | |
| #include "MozFramebuffer.h"
 | |
| #include "GLContext.h"
 | |
| #include "mozilla/CheckedInt.h"
 | |
| #include "mozilla/UniquePtrExtensions.h"
 | |
| #include "nsPrintfCString.h"
 | |
| #include "WebGLBuffer.h"
 | |
| #include "WebGLContextUtils.h"
 | |
| #include "WebGLFramebuffer.h"
 | |
| #include "WebGLProgram.h"
 | |
| #include "WebGLRenderbuffer.h"
 | |
| #include "WebGLShader.h"
 | |
| #include "WebGLTexture.h"
 | |
| #include "WebGLTransformFeedback.h"
 | |
| #include "WebGLVertexArray.h"
 | |
| #include "WebGLVertexAttribData.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| // For a Tegra workaround.
 | |
| static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
 | |
| 
 | |
| ////////////////////////////////////////
 | |
| 
 | |
| class ScopedResolveTexturesForDraw
 | |
| {
 | |
|     struct TexRebindRequest
 | |
|     {
 | |
|         uint32_t texUnit;
 | |
|         WebGLTexture* tex;
 | |
|     };
 | |
| 
 | |
|     WebGLContext* const mWebGL;
 | |
|     std::vector<TexRebindRequest> mRebindRequests;
 | |
| 
 | |
| public:
 | |
|     ScopedResolveTexturesForDraw(WebGLContext* webgl, const char* funcName,
 | |
|                                  bool* const out_error);
 | |
|     ~ScopedResolveTexturesForDraw();
 | |
| };
 | |
| 
 | |
| bool
 | |
| WebGLTexture::IsFeedback(WebGLContext* webgl, const char* funcName, uint32_t texUnit,
 | |
|                          const std::vector<const WebGLFBAttachPoint*>& fbAttachments) const
 | |
| {
 | |
|     auto itr = fbAttachments.cbegin();
 | |
|     for (; itr != fbAttachments.cend(); ++itr) {
 | |
|         const auto& attach = *itr;
 | |
|         if (attach->Texture() == this)
 | |
|             break;
 | |
|     }
 | |
| 
 | |
|     if (itr == fbAttachments.cend())
 | |
|         return false;
 | |
| 
 | |
|     ////
 | |
| 
 | |
|     const auto minLevel = mBaseMipmapLevel;
 | |
|     uint32_t maxLevel;
 | |
|     if (!MaxEffectiveMipmapLevel(texUnit, &maxLevel)) {
 | |
|         // No valid mips. Will need fake-black.
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     ////
 | |
| 
 | |
|     for (; itr != fbAttachments.cend(); ++itr) {
 | |
|         const auto& attach = *itr;
 | |
|         if (attach->Texture() != this)
 | |
|             continue;
 | |
| 
 | |
|         const auto dstLevel = attach->MipLevel();
 | |
| 
 | |
|         if (minLevel <= dstLevel && dstLevel <= maxLevel) {
 | |
|             webgl->ErrorInvalidOperation("%s: Feedback loop detected between tex target"
 | |
|                                          " 0x%04x, tex unit %u, levels %u-%u; and"
 | |
|                                          " framebuffer attachment 0x%04x, level %u.",
 | |
|                                          funcName, mTarget.get(), texUnit, minLevel,
 | |
|                                          maxLevel, attach->mAttachmentPoint, dstLevel);
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(WebGLContext* webgl,
 | |
|                                                            const char* funcName,
 | |
|                                                            bool* const out_error)
 | |
|     : mWebGL(webgl)
 | |
| {
 | |
|     MOZ_ASSERT(mWebGL->gl->IsCurrent());
 | |
| 
 | |
|     const std::vector<const WebGLFBAttachPoint*>* attachList = nullptr;
 | |
|     const auto& fb = mWebGL->mBoundDrawFramebuffer;
 | |
|     if (fb) {
 | |
|         attachList = &(fb->ResolvedCompleteData()->texDrawBuffers);
 | |
|     }
 | |
| 
 | |
|     MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
 | |
|     const auto& uniformSamplers = mWebGL->mActiveProgramLinkInfo->uniformSamplers;
 | |
|     for (const auto& uniform : uniformSamplers) {
 | |
|         const auto& texList = *(uniform->mSamplerTexList);
 | |
| 
 | |
|         for (const auto& texUnit : uniform->mSamplerValues) {
 | |
|             if (texUnit >= texList.Length())
 | |
|                 continue;
 | |
| 
 | |
|             const auto& tex = texList[texUnit];
 | |
|             if (!tex)
 | |
|                 continue;
 | |
| 
 | |
|             if (attachList &&
 | |
|                 tex->IsFeedback(mWebGL, funcName, texUnit, *attachList))
 | |
|             {
 | |
|                 *out_error = true;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             FakeBlackType fakeBlack;
 | |
|             if (!tex->ResolveForDraw(funcName, texUnit, &fakeBlack)) {
 | |
|                 mWebGL->ErrorOutOfMemory("%s: Failed to resolve textures for draw.",
 | |
|                                          funcName);
 | |
|                 *out_error = true;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (fakeBlack == FakeBlackType::None)
 | |
|                 continue;
 | |
| 
 | |
|             if (!mWebGL->BindFakeBlack(texUnit, tex->Target(), fakeBlack)) {
 | |
|                 mWebGL->ErrorOutOfMemory("%s: Failed to create fake black texture.",
 | |
|                                          funcName);
 | |
|                 *out_error = true;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             mRebindRequests.push_back({texUnit, tex});
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     *out_error = false;
 | |
| }
 | |
| 
 | |
| ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw()
 | |
| {
 | |
|     if (mRebindRequests.empty())
 | |
|         return;
 | |
| 
 | |
|     gl::GLContext* gl = mWebGL->gl;
 | |
| 
 | |
|     for (const auto& itr : mRebindRequests) {
 | |
|         gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
 | |
|         gl->fBindTexture(itr.tex->Target().get(), itr.tex->mGLName);
 | |
|     }
 | |
| 
 | |
|     gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mWebGL->mActiveTexture);
 | |
| }
 | |
| 
 | |
| bool
 | |
| WebGLContext::BindFakeBlack(uint32_t texUnit, TexTarget target, FakeBlackType fakeBlack)
 | |
| {
 | |
|     MOZ_ASSERT(fakeBlack == FakeBlackType::RGBA0000 ||
 | |
|                fakeBlack == FakeBlackType::RGBA0001);
 | |
| 
 | |
|     const auto fnGetSlot = [this, target, fakeBlack]() -> UniquePtr<FakeBlackTexture>*
 | |
|     {
 | |
|         switch (fakeBlack) {
 | |
|         case FakeBlackType::RGBA0000:
 | |
|             switch (target.get()) {
 | |
|             case LOCAL_GL_TEXTURE_2D      : return &mFakeBlack_2D_0000;
 | |
|             case LOCAL_GL_TEXTURE_CUBE_MAP: return &mFakeBlack_CubeMap_0000;
 | |
|             case LOCAL_GL_TEXTURE_3D      : return &mFakeBlack_3D_0000;
 | |
|             case LOCAL_GL_TEXTURE_2D_ARRAY: return &mFakeBlack_2D_Array_0000;
 | |
|             default: return nullptr;
 | |
|             }
 | |
| 
 | |
|         case FakeBlackType::RGBA0001:
 | |
|             switch (target.get()) {
 | |
|             case LOCAL_GL_TEXTURE_2D      : return &mFakeBlack_2D_0001;
 | |
|             case LOCAL_GL_TEXTURE_CUBE_MAP: return &mFakeBlack_CubeMap_0001;
 | |
|             case LOCAL_GL_TEXTURE_3D      : return &mFakeBlack_3D_0001;
 | |
|             case LOCAL_GL_TEXTURE_2D_ARRAY: return &mFakeBlack_2D_Array_0001;
 | |
|             default: return nullptr;
 | |
|             }
 | |
| 
 | |
|         default:
 | |
|             return nullptr;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     UniquePtr<FakeBlackTexture>* slot = fnGetSlot();
 | |
|     if (!slot) {
 | |
|         MOZ_CRASH("GFX: fnGetSlot failed.");
 | |
|     }
 | |
|     UniquePtr<FakeBlackTexture>& fakeBlackTex = *slot;
 | |
| 
 | |
|     if (!fakeBlackTex) {
 | |
|         gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
 | |
|         if (IsWebGL2()) {
 | |
|             gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS, 0);
 | |
|             gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, 0);
 | |
|             gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0);
 | |
|         }
 | |
| 
 | |
|         fakeBlackTex = FakeBlackTexture::Create(gl, target, fakeBlack);
 | |
| 
 | |
|         gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, mPixelStore_UnpackAlignment);
 | |
|         if (IsWebGL2()) {
 | |
|             gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS, mPixelStore_UnpackSkipPixels);
 | |
|             gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, mPixelStore_UnpackSkipRows);
 | |
|             gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mPixelStore_UnpackSkipImages);
 | |
|         }
 | |
|         if (!fakeBlackTex) {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     gl->fActiveTexture(LOCAL_GL_TEXTURE0 + texUnit);
 | |
|     gl->fBindTexture(target.get(), fakeBlackTex->mGLName);
 | |
|     gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////
 | |
| 
 | |
| bool
 | |
| WebGLContext::ValidateStencilParamsForDrawCall(const char* const funcName) const
 | |
| {
 | |
|     const auto stencilBits = [&]() -> uint8_t {
 | |
|         if (!mStencilTestEnabled)
 | |
|             return 0;
 | |
| 
 | |
|         if (!mBoundDrawFramebuffer)
 | |
|             return mOptions.stencil ? 8 : 0;
 | |
| 
 | |
|         if (mBoundDrawFramebuffer->StencilAttachment().IsDefined())
 | |
|             return 8;
 | |
| 
 | |
|         if (mBoundDrawFramebuffer->DepthStencilAttachment().IsDefined())
 | |
|             return 8;
 | |
| 
 | |
|         return 0;
 | |
|     }();
 | |
|     const uint32_t stencilMax = (1 << stencilBits) - 1;
 | |
| 
 | |
|     const auto fnMask = [&](const uint32_t x) { return x & stencilMax; };
 | |
|     const auto fnClamp = [&](const int32_t x) {
 | |
|         return std::max(0, std::min(x, (int32_t)stencilMax));
 | |
|     };
 | |
| 
 | |
|     bool ok = true;
 | |
|     ok &= (fnMask(mStencilWriteMaskFront) == fnMask(mStencilWriteMaskBack));
 | |
|     ok &= (fnMask(mStencilValueMaskFront) == fnMask(mStencilValueMaskBack));
 | |
|     ok &= (fnClamp(mStencilRefFront) == fnClamp(mStencilRefBack));
 | |
| 
 | |
|     if (!ok) {
 | |
|         ErrorInvalidOperation("%s: Stencil front/back state must effectively match."
 | |
|                               " (before front/back comparison, WRITEMASK and VALUE_MASK"
 | |
|                               " are masked with (2^s)-1, and REF is clamped to"
 | |
|                               " [0, (2^s)-1], where `s` is the number of enabled stencil"
 | |
|                               " bits in the draw framebuffer)",
 | |
|                               funcName);
 | |
|     }
 | |
|     return ok;
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////
 | |
| 
 | |
| template<typename T>
 | |
| static bool
 | |
| DoSetsIntersect(const std::set<T>& a, const std::set<T>& b)
 | |
| {
 | |
|     std::vector<T> intersection;
 | |
|     std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
 | |
|                           std::back_inserter(intersection));
 | |
|     return bool(intersection.size());
 | |
| }
 | |
| 
 | |
| class ScopedDrawHelper final
 | |
| {
 | |
|     WebGLContext* const mWebGL;
 | |
|     bool mDidFake;
 | |
| 
 | |
| public:
 | |
|     ScopedDrawHelper(WebGLContext* const webgl, const char* const funcName,
 | |
|                      const GLenum mode, const Maybe<uint32_t>& lastRequiredVertex,
 | |
|                      const uint32_t instanceCount, bool* const out_error)
 | |
|         : mWebGL(webgl)
 | |
|         , mDidFake(false)
 | |
|     {
 | |
|         MOZ_ASSERT(mWebGL->gl->IsCurrent());
 | |
| 
 | |
|         if (!mWebGL->BindCurFBForDraw(funcName)) {
 | |
|             *out_error = true;
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (!mWebGL->ValidateDrawModeEnum(mode, funcName)) {
 | |
|             *out_error = true;
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (!mWebGL->ValidateStencilParamsForDrawCall(funcName)) {
 | |
|             *out_error = true;
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (!mWebGL->mActiveProgramLinkInfo) {
 | |
|             mWebGL->ErrorInvalidOperation("%s: The current program is not linked.", funcName);
 | |
|             *out_error = true;
 | |
|             return;
 | |
|         }
 | |
|         const auto& linkInfo = mWebGL->mActiveProgramLinkInfo;
 | |
| 
 | |
|         ////
 | |
|         // Check UBO sizes.
 | |
| 
 | |
|         for (const auto& cur : linkInfo->uniformBlocks) {
 | |
|             const auto& dataSize = cur->mDataSize;
 | |
|             const auto& binding = cur->mBinding;
 | |
|             if (!binding) {
 | |
|                 mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is null.",
 | |
|                                               funcName);
 | |
|                 *out_error = true;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             const auto availByteCount = binding->ByteCount();
 | |
|             if (dataSize > availByteCount) {
 | |
|                 mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is smaller"
 | |
|                                               " than UNIFORM_BLOCK_DATA_SIZE.",
 | |
|                                               funcName);
 | |
|                 *out_error = true;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (binding->mBufferBinding->IsBoundForTF()) {
 | |
|                 mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is bound or"
 | |
|                                               " in use for transform feedback.",
 | |
|                                               funcName);
 | |
|                 *out_error = true;
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         ////
 | |
| 
 | |
|         const auto& tfo = mWebGL->mBoundTransformFeedback;
 | |
|         if (tfo && tfo->IsActiveAndNotPaused()) {
 | |
|             uint32_t numUsed;
 | |
|             switch (linkInfo->transformFeedbackBufferMode) {
 | |
|             case LOCAL_GL_INTERLEAVED_ATTRIBS:
 | |
|                 numUsed = 1;
 | |
|                 break;
 | |
| 
 | |
|             case LOCAL_GL_SEPARATE_ATTRIBS:
 | |
|                 numUsed = linkInfo->transformFeedbackVaryings.size();
 | |
|                 break;
 | |
| 
 | |
|             default:
 | |
|                 MOZ_CRASH();
 | |
|             }
 | |
| 
 | |
|             for (uint32_t i = 0; i < numUsed; ++i) {
 | |
|                 const auto& buffer = tfo->mIndexedBindings[i].mBufferBinding;
 | |
|                 if (buffer->IsBoundForNonTF()) {
 | |
|                     mWebGL->ErrorInvalidOperation("%s: Transform feedback varying %u's"
 | |
|                                                   " buffer is bound for"
 | |
|                                                   " non-transform-feedback.",
 | |
|                                                   funcName, i);
 | |
|                     *out_error = true;
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 // Technically we don't know that this will be updated yet, but we can
 | |
|                 // speculatively mark it.
 | |
|                 buffer->ResetLastUpdateFenceId();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         ////
 | |
| 
 | |
|         const auto& fetchLimits = linkInfo->GetDrawFetchLimits(funcName);
 | |
|         if (!fetchLimits) {
 | |
|             *out_error = true;
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (lastRequiredVertex && instanceCount) {
 | |
|             if (lastRequiredVertex.value() >= fetchLimits->maxVerts) {
 | |
|                 mWebGL->ErrorInvalidOperation("%s: Vertex fetch requires vertex #%u, but"
 | |
|                                               " attribs only supply %" PRIu64 ".",
 | |
|                                               funcName, lastRequiredVertex.value(),
 | |
|                                               fetchLimits->maxVerts);
 | |
|                 *out_error = true;
 | |
|                 return;
 | |
|             }
 | |
|             if (instanceCount > fetchLimits->maxInstances) {
 | |
|                 mWebGL->ErrorInvalidOperation("%s: Instance fetch requires %u, but"
 | |
|                                               " attribs only supply %" PRIu64 ".",
 | |
|                                               funcName, instanceCount,
 | |
|                                               fetchLimits->maxInstances);
 | |
|                 *out_error = true;
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         ////
 | |
| 
 | |
|         if (lastRequiredVertex) {
 | |
|             if (!mWebGL->DoFakeVertexAttrib0(funcName, lastRequiredVertex.value())) {
 | |
|                 *out_error = true;
 | |
|                 return;
 | |
|             }
 | |
|             mDidFake = true;
 | |
|         }
 | |
| 
 | |
|         ////
 | |
| 
 | |
|         mWebGL->RunContextLossTimer();
 | |
|     }
 | |
| 
 | |
|     ~ScopedDrawHelper() {
 | |
|         if (mDidFake) {
 | |
|             mWebGL->UndoFakeVertexAttrib0();
 | |
|         }
 | |
|     }
 | |
| };
 | |
| 
 | |
| ////////////////////////////////////////
 | |
| 
 | |
| static uint32_t
 | |
| UsedVertsForTFDraw(GLenum mode, uint32_t vertCount)
 | |
| {
 | |
|     uint8_t vertsPerPrim;
 | |
| 
 | |
|     switch (mode) {
 | |
|     case LOCAL_GL_POINTS:
 | |
|         vertsPerPrim = 1;
 | |
|         break;
 | |
|     case LOCAL_GL_LINES:
 | |
|         vertsPerPrim = 2;
 | |
|         break;
 | |
|     case LOCAL_GL_TRIANGLES:
 | |
|         vertsPerPrim = 3;
 | |
|         break;
 | |
|     default:
 | |
|         MOZ_CRASH("`mode`");
 | |
|     }
 | |
| 
 | |
|     return vertCount / vertsPerPrim * vertsPerPrim;
 | |
| }
 | |
| 
 | |
| class ScopedDrawWithTransformFeedback final
 | |
| {
 | |
|     WebGLContext* const mWebGL;
 | |
|     WebGLTransformFeedback* const mTFO;
 | |
|     const bool mWithTF;
 | |
|     uint32_t mUsedVerts;
 | |
| 
 | |
| public:
 | |
|     ScopedDrawWithTransformFeedback(WebGLContext* webgl, const char* funcName,
 | |
|                                     GLenum mode, uint32_t vertCount,
 | |
|                                     uint32_t instanceCount, bool* const out_error)
 | |
|         : mWebGL(webgl)
 | |
|         , mTFO(mWebGL->mBoundTransformFeedback)
 | |
|         , mWithTF(mTFO &&
 | |
|                   mTFO->mIsActive &&
 | |
|                   !mTFO->mIsPaused)
 | |
|         , mUsedVerts(0)
 | |
|     {
 | |
|         *out_error = false;
 | |
|         if (!mWithTF)
 | |
|             return;
 | |
| 
 | |
|         if (mode != mTFO->mActive_PrimMode) {
 | |
|             mWebGL->ErrorInvalidOperation("%s: Drawing with transform feedback requires"
 | |
|                                           " `mode` to match BeginTransformFeedback's"
 | |
|                                           " `primitiveMode`.",
 | |
|                                           funcName);
 | |
|             *out_error = true;
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
 | |
|         const auto usedVerts = CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
 | |
| 
 | |
|         const auto remainingCapacity = mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
 | |
|         if (!usedVerts.isValid() ||
 | |
|             usedVerts.value() > remainingCapacity)
 | |
|         {
 | |
|             mWebGL->ErrorInvalidOperation("%s: Insufficient buffer capacity remaining for"
 | |
|                                           " transform feedback.",
 | |
|                                           funcName);
 | |
|             *out_error = true;
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         mUsedVerts = usedVerts.value();
 | |
|     }
 | |
| 
 | |
|     void Advance() const {
 | |
|         if (!mWithTF)
 | |
|             return;
 | |
| 
 | |
|         mTFO->mActive_VertPosition += mUsedVerts;
 | |
|     }
 | |
| };
 | |
| 
 | |
| static bool
 | |
| HasInstancedDrawing(const WebGLContext& webgl)
 | |
| {
 | |
|     return webgl.IsWebGL2() ||
 | |
|            webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////
 | |
| 
 | |
| bool
 | |
| WebGLContext::DrawArrays_check(const char* const funcName, const GLint first,
 | |
|                                const GLsizei vertCount, const GLsizei instanceCount,
 | |
|                                Maybe<uint32_t>* const out_lastVert)
 | |
| {
 | |
|     if (!ValidateNonNegative(funcName, "first", first) ||
 | |
|         !ValidateNonNegative(funcName, "vertCount", vertCount) ||
 | |
|         !ValidateNonNegative(funcName, "instanceCount", instanceCount))
 | |
|     {
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
 | |
|         MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
 | |
|         if (mPrimRestartTypeBytes != 0) {
 | |
|             mPrimRestartTypeBytes = 0;
 | |
| 
 | |
|             // OSX appears to have severe perf issues with leaving this enabled.
 | |
|             gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (!vertCount) {
 | |
|         *out_lastVert = Nothing();
 | |
|     } else {
 | |
|         const auto lastVert_checked = CheckedInt<uint32_t>(first) + vertCount - 1;
 | |
|         if (!lastVert_checked.isValid()) {
 | |
|             ErrorOutOfMemory("%s: `first+vertCount` out of range.", funcName);
 | |
|             return false;
 | |
|         }
 | |
|         *out_lastVert = Some(lastVert_checked.value());
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| void
 | |
| WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei vertCount,
 | |
|                                   GLsizei instanceCount, const char* const funcName)
 | |
| {
 | |
|     AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
 | |
|     if (IsContextLost())
 | |
|         return;
 | |
| 
 | |
|     const gl::GLContext::TlsScope inTls(gl);
 | |
| 
 | |
|     Maybe<uint32_t> lastVert;
 | |
|     if (!DrawArrays_check(funcName, first, vertCount, instanceCount, &lastVert))
 | |
|         return;
 | |
| 
 | |
|     bool error = false;
 | |
|     const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount,
 | |
|                                         &error);
 | |
|     if (error)
 | |
|         return;
 | |
| 
 | |
|     const ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
 | |
|     if (error)
 | |
|         return;
 | |
| 
 | |
|     const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
 | |
|                                                    instanceCount, &error);
 | |
|     if (error)
 | |
|         return;
 | |
| 
 | |
|     {
 | |
|         ScopedDrawCallWrapper wrapper(*this);
 | |
|         if (vertCount && instanceCount) {
 | |
|             AUTO_PROFILER_LABEL("glDrawArraysInstanced", GRAPHICS);
 | |
|             if (HasInstancedDrawing(*this)) {
 | |
|                 gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount);
 | |
|             } else {
 | |
|                 MOZ_ASSERT(instanceCount == 1);
 | |
|                 gl->fDrawArrays(mode, first, vertCount);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Draw_cleanup(funcName);
 | |
|     scopedTF.Advance();
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////
 | |
| 
 | |
| bool
 | |
| WebGLContext::DrawElements_check(const char* const funcName, const GLsizei rawIndexCount,
 | |
|                                  const GLenum type, const WebGLintptr byteOffset,
 | |
|                                  const GLsizei instanceCount,
 | |
|                                  Maybe<uint32_t>* const out_lastVert)
 | |
| {
 | |
|     if (mBoundTransformFeedback &&
 | |
|         mBoundTransformFeedback->mIsActive &&
 | |
|         !mBoundTransformFeedback->mIsPaused)
 | |
|     {
 | |
|         ErrorInvalidOperation("%s: DrawElements* functions are incompatible with"
 | |
|                               " transform feedback.",
 | |
|                               funcName);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     if (!ValidateNonNegative(funcName, "vertCount", rawIndexCount) ||
 | |
|         !ValidateNonNegative(funcName, "byteOffset", byteOffset) ||
 | |
|         !ValidateNonNegative(funcName, "instanceCount", instanceCount))
 | |
|     {
 | |
|         return false;
 | |
|     }
 | |
|     const auto indexCount = uint32_t(rawIndexCount);
 | |
| 
 | |
|     uint8_t bytesPerIndex = 0;
 | |
|     switch (type) {
 | |
|     case LOCAL_GL_UNSIGNED_BYTE:
 | |
|         bytesPerIndex = 1;
 | |
|         break;
 | |
| 
 | |
|     case LOCAL_GL_UNSIGNED_SHORT:
 | |
|         bytesPerIndex = 2;
 | |
|         break;
 | |
| 
 | |
|     case LOCAL_GL_UNSIGNED_INT:
 | |
|         if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
 | |
|             bytesPerIndex = 4;
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|     if (!bytesPerIndex) {
 | |
|         ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", funcName, type);
 | |
|         return false;
 | |
|     }
 | |
|     if (byteOffset % bytesPerIndex != 0) {
 | |
|         ErrorInvalidOperation("%s: `byteOffset` must be a multiple of the size of `type`",
 | |
|                               funcName);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     ////
 | |
| 
 | |
|     if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
 | |
|         MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
 | |
|         if (mPrimRestartTypeBytes != bytesPerIndex) {
 | |
|             mPrimRestartTypeBytes = bytesPerIndex;
 | |
| 
 | |
|             const uint32_t ones = UINT32_MAX >> (32 - 8*mPrimRestartTypeBytes);
 | |
|             gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
 | |
|             gl->fPrimitiveRestartIndex(ones);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     ////
 | |
|     // Index fetching
 | |
| 
 | |
|     const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
 | |
|     if (!indexBuffer) {
 | |
|         ErrorInvalidOperation("%s: Index buffer not bound.", funcName);
 | |
|         return false;
 | |
|     }
 | |
|     MOZ_ASSERT(!indexBuffer->IsBoundForTF(), "This should be impossible.");
 | |
| 
 | |
|     if (!indexCount || !instanceCount) {
 | |
|         *out_lastVert = Nothing();
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     const size_t availBytes = indexBuffer->ByteLength();
 | |
|     const auto availIndices = AvailGroups(availBytes, byteOffset, bytesPerIndex,
 | |
|                                           bytesPerIndex);
 | |
|     if (indexCount > availIndices) {
 | |
|         ErrorInvalidOperation("%s: Index buffer too small.", funcName);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     *out_lastVert = indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static void
 | |
| HandleDrawElementsErrors(WebGLContext* webgl, const char* funcName,
 | |
|                          gl::GLContext::LocalErrorScope& errorScope)
 | |
| {
 | |
|     const auto err = errorScope.GetError();
 | |
|     if (err == LOCAL_GL_INVALID_OPERATION) {
 | |
|         webgl->ErrorInvalidOperation("%s: Driver rejected indexed draw call, possibly"
 | |
|                                      " due to out-of-bounds indices.", funcName);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     MOZ_ASSERT(!err);
 | |
|     if (err) {
 | |
|         webgl->ErrorImplementationBug("%s: Unexpected driver error during indexed draw"
 | |
|                                       " call. Please file a bug.",
 | |
|                                       funcName);
 | |
|         return;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei indexCount, GLenum type,
 | |
|                                     WebGLintptr byteOffset, GLsizei instanceCount,
 | |
|                                     const char* const funcName)
 | |
| {
 | |
|     AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
 | |
|     if (IsContextLost())
 | |
|         return;
 | |
| 
 | |
|     const gl::GLContext::TlsScope inTls(gl);
 | |
| 
 | |
|     Maybe<uint32_t> lastVert;
 | |
|     if (!DrawElements_check(funcName, indexCount, type, byteOffset, instanceCount,
 | |
|                             &lastVert))
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     bool error = false;
 | |
|     const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount,
 | |
|                                         &error);
 | |
|     if (error)
 | |
|         return;
 | |
| 
 | |
|     const ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
 | |
|     if (error)
 | |
|         return;
 | |
| 
 | |
|     {
 | |
|         ScopedDrawCallWrapper wrapper(*this);
 | |
|         {
 | |
|             UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
 | |
|             if (MOZ_UNLIKELY( gl->IsANGLE() &&
 | |
|                               gl->mDebugFlags & gl::GLContext::DebugFlagAbortOnError ))
 | |
|             {
 | |
|                 // ANGLE does range validation even when it doesn't need to.
 | |
|                 // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
 | |
|                 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
 | |
|             }
 | |
| 
 | |
|             if (indexCount && instanceCount) {
 | |
|                 AUTO_PROFILER_LABEL("glDrawElementsInstanced", GRAPHICS);
 | |
|                 if (HasInstancedDrawing(*this)) {
 | |
|                     gl->fDrawElementsInstanced(mode, indexCount, type,
 | |
|                                                reinterpret_cast<GLvoid*>(byteOffset),
 | |
|                                                instanceCount);
 | |
|                 } else {
 | |
|                     MOZ_ASSERT(instanceCount == 1);
 | |
|                     gl->fDrawElements(mode, indexCount, type,
 | |
|                                       reinterpret_cast<GLvoid*>(byteOffset));
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (errorScope) {
 | |
|                 HandleDrawElementsErrors(this, funcName, *errorScope);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Draw_cleanup(funcName);
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////
 | |
| 
 | |
| void
 | |
| WebGLContext::Draw_cleanup(const char* funcName)
 | |
| {
 | |
|     if (gl->WorkAroundDriverBugs()) {
 | |
|         if (gl->Renderer() == gl::GLRenderer::Tegra) {
 | |
|             mDrawCallsSinceLastFlush++;
 | |
| 
 | |
|             if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
 | |
|                 gl->fFlush();
 | |
|                 mDrawCallsSinceLastFlush = 0;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Let's check for a really common error: Viewport is larger than the actual
 | |
|     // destination framebuffer.
 | |
|     uint32_t destWidth = mViewportWidth;
 | |
|     uint32_t destHeight = mViewportHeight;
 | |
| 
 | |
|     if (mBoundDrawFramebuffer) {
 | |
|         const auto& drawBuffers = mBoundDrawFramebuffer->ColorDrawBuffers();
 | |
|         for (const auto& cur : drawBuffers) {
 | |
|             if (!cur->IsDefined())
 | |
|                 continue;
 | |
|             cur->Size(&destWidth, &destHeight);
 | |
|             break;
 | |
|         }
 | |
|     } else {
 | |
|         destWidth = mDefaultFB->mSize.width;
 | |
|         destHeight = mDefaultFB->mSize.height;
 | |
|     }
 | |
| 
 | |
|     if (mViewportWidth > int32_t(destWidth) ||
 | |
|         mViewportHeight > int32_t(destHeight))
 | |
|     {
 | |
|         if (!mAlreadyWarnedAboutViewportLargerThanDest) {
 | |
|             GenerateWarning("%s: Drawing to a destination rect smaller than the viewport"
 | |
|                             " rect. (This warning will only be given once)",
 | |
|                             funcName);
 | |
|             mAlreadyWarnedAboutViewportLargerThanDest = true;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| WebGLVertexAttrib0Status
 | |
| WebGLContext::WhatDoesVertexAttrib0Need() const
 | |
| {
 | |
|     MOZ_ASSERT(mCurrentProgram);
 | |
|     MOZ_ASSERT(mActiveProgramLinkInfo);
 | |
| 
 | |
|     bool legacyAttrib0 = gl->IsCompatibilityProfile();
 | |
| #ifdef XP_MACOSX
 | |
|     if (gl->WorkAroundDriverBugs()) {
 | |
|         // Failures in conformance/attribs/gl-disabled-vertex-attrib.
 | |
|         // Even in Core profiles on NV. Sigh.
 | |
|         legacyAttrib0 |= (gl->Vendor() == gl::GLVendor::NVIDIA);
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     if (!legacyAttrib0)
 | |
|         return WebGLVertexAttrib0Status::Default;
 | |
| 
 | |
|     if (!mActiveProgramLinkInfo->attrib0Active) {
 | |
|         // Ensure that the legacy code has enough buffer.
 | |
|         return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
 | |
|     }
 | |
| 
 | |
|     const auto& isAttribArray0Enabled = mBoundVertexArray->mAttribs[0].mEnabled;
 | |
|     return isAttribArray0Enabled ? WebGLVertexAttrib0Status::Default
 | |
|                                  : WebGLVertexAttrib0Status::EmulatedInitializedArray;
 | |
| }
 | |
| 
 | |
| bool
 | |
| WebGLContext::DoFakeVertexAttrib0(const char* const funcName, const uint32_t lastVert)
 | |
| {
 | |
|     const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
 | |
|     if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
 | |
|         return true;
 | |
| 
 | |
|     if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
 | |
|         GenerateWarning("Drawing without vertex attrib 0 array enabled forces the browser "
 | |
|                         "to do expensive emulation work when running on desktop OpenGL "
 | |
|                         "platforms, for example on Mac. It is preferable to always draw "
 | |
|                         "with vertex attrib 0 array enabled, by using bindAttribLocation "
 | |
|                         "to bind some always-used attribute to location 0.");
 | |
|         mAlreadyWarnedAboutFakeVertexAttrib0 = true;
 | |
|     }
 | |
| 
 | |
|     gl->fEnableVertexAttribArray(0);
 | |
| 
 | |
|     if (!mFakeVertexAttrib0BufferObject) {
 | |
|         gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
 | |
|         mFakeVertexAttrib0BufferObjectSize = 0;
 | |
|     }
 | |
|     gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
 | |
| 
 | |
|     ////
 | |
| 
 | |
|     switch (mGenericVertexAttribTypes[0]) {
 | |
|     case LOCAL_GL_FLOAT:
 | |
|         gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
 | |
|         break;
 | |
| 
 | |
|     case LOCAL_GL_INT:
 | |
|         gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
 | |
|         break;
 | |
| 
 | |
|     case LOCAL_GL_UNSIGNED_INT:
 | |
|         gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         MOZ_CRASH();
 | |
|     }
 | |
| 
 | |
|     ////
 | |
| 
 | |
|     const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
 | |
|     const auto checked_dataSize = (CheckedUint32(lastVert)+1) * bytesPerVert;
 | |
|     if (!checked_dataSize.isValid()) {
 | |
|         ErrorOutOfMemory("Integer overflow trying to construct a fake vertex attrib 0"
 | |
|                          " array for a draw-operation with %" PRIu64 " vertices. Try"
 | |
|                          " reducing the number of vertices.",
 | |
|                          uint64_t(lastVert) + 1);
 | |
|         return false;
 | |
|     }
 | |
|     const auto dataSize = checked_dataSize.value();
 | |
| 
 | |
|     if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
 | |
|         gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr, LOCAL_GL_DYNAMIC_DRAW);
 | |
|         mFakeVertexAttrib0BufferObjectSize = dataSize;
 | |
|         mFakeVertexAttrib0DataDefined = false;
 | |
|     }
 | |
| 
 | |
|     if (whatDoesAttrib0Need == WebGLVertexAttrib0Status::EmulatedUninitializedArray)
 | |
|         return true;
 | |
| 
 | |
|     ////
 | |
| 
 | |
|     if (mFakeVertexAttrib0DataDefined &&
 | |
|         memcmp(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert) == 0)
 | |
|     {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     ////
 | |
| 
 | |
|     const UniqueBuffer data(malloc(dataSize));
 | |
|     if (!data) {
 | |
|         ErrorOutOfMemory("%s: Failed to allocate fake vertex attrib 0 array.",
 | |
|                          funcName);
 | |
|         return false;
 | |
|     }
 | |
|     auto itr = (uint8_t*)data.get();
 | |
|     const auto itrEnd = itr + dataSize;
 | |
|     while (itr != itrEnd) {
 | |
|         memcpy(itr, mGenericVertexAttrib0Data, bytesPerVert);
 | |
|         itr += bytesPerVert;
 | |
|     }
 | |
| 
 | |
|     {
 | |
|         gl::GLContext::LocalErrorScope errorScope(*gl);
 | |
| 
 | |
|         gl->fBufferSubData(LOCAL_GL_ARRAY_BUFFER, 0, dataSize, data.get());
 | |
| 
 | |
|         const auto err = errorScope.GetError();
 | |
|         if (err) {
 | |
|             ErrorOutOfMemory("%s: Failed to upload fake vertex attrib 0 data.", funcName);
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     ////
 | |
| 
 | |
|     memcpy(mFakeVertexAttrib0Data, mGenericVertexAttrib0Data, bytesPerVert);
 | |
|     mFakeVertexAttrib0DataDefined = true;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| void
 | |
| WebGLContext::UndoFakeVertexAttrib0()
 | |
| {
 | |
|     const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
 | |
|     if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
 | |
|         return;
 | |
| 
 | |
|     if (mBoundVertexArray->mAttribs[0].mBuf) {
 | |
|         const WebGLVertexAttribData& attrib0 = mBoundVertexArray->mAttribs[0];
 | |
|         gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0.mBuf->mGLName);
 | |
|         attrib0.DoVertexAttribPointer(gl, 0);
 | |
|     } else {
 | |
|         gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
 | |
|     }
 | |
| 
 | |
|     gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundArrayBuffer ? mBoundArrayBuffer->mGLName : 0);
 | |
| }
 | |
| 
 | |
| static GLuint
 | |
| CreateGLTexture(gl::GLContext* gl)
 | |
| {
 | |
|     MOZ_ASSERT(gl->IsCurrent());
 | |
|     GLuint ret = 0;
 | |
|     gl->fGenTextures(1, &ret);
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| UniquePtr<WebGLContext::FakeBlackTexture>
 | |
| WebGLContext::FakeBlackTexture::Create(gl::GLContext* gl, TexTarget target,
 | |
|                                        FakeBlackType type)
 | |
| {
 | |
|     GLenum texFormat;
 | |
|     switch (type) {
 | |
|     case FakeBlackType::RGBA0000:
 | |
|         texFormat = LOCAL_GL_RGBA;
 | |
|         break;
 | |
| 
 | |
|     case FakeBlackType::RGBA0001:
 | |
|         texFormat = LOCAL_GL_RGB;
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         MOZ_CRASH("GFX: bad type");
 | |
|     }
 | |
| 
 | |
|     UniquePtr<FakeBlackTexture> result(new FakeBlackTexture(gl));
 | |
|     gl::ScopedBindTexture scopedBind(gl, result->mGLName, target.get());
 | |
| 
 | |
|     gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST);
 | |
|     gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST);
 | |
| 
 | |
|     const webgl::DriverUnpackInfo dui = {texFormat, texFormat, LOCAL_GL_UNSIGNED_BYTE};
 | |
|     UniqueBuffer zeros = moz_xcalloc(1, 4); // Infallible allocation.
 | |
| 
 | |
|     MOZ_ASSERT(gl->IsCurrent());
 | |
| 
 | |
|     if (target == LOCAL_GL_TEXTURE_CUBE_MAP) {
 | |
|         for (int i = 0; i < 6; ++i) {
 | |
|             const TexImageTarget curTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
 | |
|             const GLenum error = DoTexImage(gl, curTarget.get(), 0, &dui, 1, 1, 1,
 | |
|                                             zeros.get());
 | |
|             if (error) {
 | |
|                 return nullptr;
 | |
|             }
 | |
|         }
 | |
|     } else {
 | |
|         const GLenum error = DoTexImage(gl, target.get(), 0, &dui, 1, 1, 1,
 | |
|                                         zeros.get());
 | |
|         if (error) {
 | |
|             return nullptr;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| WebGLContext::FakeBlackTexture::FakeBlackTexture(gl::GLContext* gl)
 | |
|     : mGL(gl)
 | |
|     , mGLName(CreateGLTexture(gl))
 | |
| {
 | |
| }
 | |
| 
 | |
| WebGLContext::FakeBlackTexture::~FakeBlackTexture()
 | |
| {
 | |
|     mGL->fDeleteTextures(1, &mGLName);
 | |
| }
 | |
| 
 | |
| } // namespace mozilla
 |