/* -*- 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 "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 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 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& 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()); if (!mWebGL->mActiveProgramLinkInfo) { mWebGL->ErrorInvalidOperation("%s: The current program is not linked.", funcName); *out_error = true; return; } const std::vector* attachList = nullptr; const auto& fb = mWebGL->mBoundDrawFramebuffer; if (fb) { if (!fb->ValidateAndInitAttachments(funcName)) { *out_error = true; return; } attachList = &(fb->ResolvedCompleteData()->texDrawBuffers); } else { webgl->ClearBackbufferIfNeeded(); } 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* { 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* slot = fnGetSlot(); if (!slot) { MOZ_CRASH("GFX: fnGetSlot failed."); } UniquePtr& fakeBlackTex = *slot; if (!fakeBlackTex) { fakeBlackTex = FakeBlackTexture::Create(gl, target, fakeBlack); 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::DrawInstanced_check(const char* info) { MOZ_ASSERT(IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays)); if (!mBufferFetchingHasPerVertex) { /* http://www.khronos.org/registry/gles/extensions/ANGLE/ANGLE_instanced_arrays.txt * If all of the enabled vertex attribute arrays that are bound to active * generic attributes in the program have a non-zero divisor, the draw * call should return INVALID_OPERATION. * * NB: This also appears to apply to NV_instanced_arrays, though the * INVALID_OPERATION emission is not explicitly stated. * ARB_instanced_arrays does not have this restriction. */ ErrorInvalidOperation("%s: at least one vertex attribute divisor should be 0", info); return false; } return true; } bool WebGLContext::DrawArrays_check(const char* funcName, GLenum mode, GLint first, GLsizei vertCount, GLsizei instanceCount) { if (!ValidateDrawModeEnum(mode, funcName)) return false; if (!ValidateNonNegative(funcName, "first", first) || !ValidateNonNegative(funcName, "vertCount", vertCount) || !ValidateNonNegative(funcName, "instanceCount", instanceCount)) { return false; } if (!ValidateStencilParamsForDrawCall()) 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 || !instanceCount) return false; // No error, just early out. if (!ValidateBufferFetching(funcName)) return false; const auto checked_firstPlusCount = CheckedInt(first) + vertCount; if (!checked_firstPlusCount.isValid()) { ErrorInvalidOperation("%s: overflow in first+vertCount", funcName); return false; } if (uint32_t(checked_firstPlusCount.value()) > mMaxFetchedVertices) { ErrorInvalidOperation("%s: Bound vertex attribute buffers do not have sufficient" " size for given first and count.", funcName); return false; } return true; } //////////////////////////////////////// template static bool DoSetsIntersect(const std::set& a, const std::set& b) { std::vector 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* webgl, const char* funcName, uint32_t firstVertex, uint32_t vertCount, uint32_t instanceCount, bool* const out_error) : mWebGL(webgl) , mDidFake(false) { if (instanceCount > mWebGL->mMaxFetchedInstances) { mWebGL->ErrorInvalidOperation("%s: Bound instance attribute buffers do not" " have sufficient size for given" " `instanceCount`.", funcName); *out_error = true; return; } MOZ_ASSERT(mWebGL->gl->IsCurrent()); if (mWebGL->mBoundDrawFramebuffer) { if (!mWebGL->mBoundDrawFramebuffer->ValidateAndInitAttachments(funcName)) { *out_error = true; return; } } else { mWebGL->ClearBackbufferIfNeeded(); } //// const size_t requiredVerts = firstVertex + vertCount; if (!mWebGL->DoFakeVertexAttrib0(funcName, requiredVerts)) { *out_error = true; return; } mDidFake = true; //// // Check UBO sizes. const auto& linkInfo = mWebGL->mActiveProgramLinkInfo; 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; } } } //// for (const auto& progAttrib : linkInfo->attribs) { const auto& loc = progAttrib.mLoc; if (loc == -1) continue; const auto& attribData = mWebGL->mBoundVertexArray->mAttribs[loc]; GLenum attribDataBaseType; if (attribData.mEnabled) { attribDataBaseType = attribData.BaseType(); if (attribData.mBuf->IsBoundForTF()) { mWebGL->ErrorInvalidOperation("%s: Vertex attrib %u's buffer is bound" " or in use for transform feedback.", funcName, loc); *out_error = true; return; } } else { attribDataBaseType = mWebGL->mGenericVertexAttribTypes[loc]; } if (attribDataBaseType != progAttrib.mBaseType) { nsCString progType, dataType; WebGLContext::EnumName(progAttrib.mBaseType, &progType); WebGLContext::EnumName(attribDataBaseType, &dataType); mWebGL->ErrorInvalidOperation("%s: Vertex attrib %u requires data of type" " %s, but is being supplied with type %s.", funcName, loc, progType.BeginReading(), dataType.BeginReading()); *out_error = true; return; } } //// 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(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; } }; //////////////////////////////////////// void WebGLContext::DrawArrays(GLenum mode, GLint first, GLsizei vertCount) { const char funcName[] = "drawArrays"; if (IsContextLost()) return; MakeContextCurrent(); bool error = false; ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error); if (error) return; const GLsizei instanceCount = 1; if (!DrawArrays_check(funcName, mode, first, vertCount, instanceCount)) return; const ScopedDrawHelper scopedHelper(this, funcName, first, vertCount, instanceCount, &error); if (error) return; const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount, instanceCount, &error); if (error) return; { ScopedDrawCallWrapper wrapper(*this); gl->fDrawArrays(mode, first, vertCount); } Draw_cleanup(funcName); scopedTF.Advance(); } void WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei vertCount, GLsizei instanceCount) { const char funcName[] = "drawArraysInstanced"; if (IsContextLost()) return; MakeContextCurrent(); bool error = false; ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error); if (error) return; if (!DrawArrays_check(funcName, mode, first, vertCount, instanceCount)) return; if (!DrawInstanced_check(funcName)) return; const ScopedDrawHelper scopedHelper(this, funcName, first, vertCount, instanceCount, &error); if (error) return; const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount, instanceCount, &error); if (error) return; { ScopedDrawCallWrapper wrapper(*this); gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount); } Draw_cleanup(funcName); scopedTF.Advance(); } //////////////////////////////////////// bool WebGLContext::DrawElements_check(const char* funcName, GLenum mode, GLsizei vertCount, GLenum type, WebGLintptr byteOffset, GLsizei instanceCount) { if (!ValidateDrawModeEnum(mode, funcName)) return false; if (mBoundTransformFeedback && mBoundTransformFeedback->mIsActive && !mBoundTransformFeedback->mIsPaused) { ErrorInvalidOperation("%s: DrawElements* functions are incompatible with" " transform feedback.", funcName); return false; } if (!ValidateNonNegative(funcName, "vertCount", vertCount) || !ValidateNonNegative(funcName, "byteOffset", byteOffset) || !ValidateNonNegative(funcName, "instanceCount", instanceCount)) { return false; } if (!ValidateStencilParamsForDrawCall()) return false; if (!vertCount || !instanceCount) return false; // No error, just early out. uint8_t bytesPerElem = 0; switch (type) { case LOCAL_GL_UNSIGNED_BYTE: bytesPerElem = 1; break; case LOCAL_GL_UNSIGNED_SHORT: bytesPerElem = 2; break; case LOCAL_GL_UNSIGNED_INT: if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) { bytesPerElem = 4; } break; } if (!bytesPerElem) { ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", funcName, type); return false; } if (byteOffset % bytesPerElem != 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 != bytesPerElem) { mPrimRestartTypeBytes = bytesPerElem; const uint32_t ones = UINT32_MAX >> (32 - 8*mPrimRestartTypeBytes); gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART); gl->fPrimitiveRestartIndex(ones); } } //// const GLsizei first = byteOffset / bytesPerElem; const CheckedUint32 checked_byteCount = bytesPerElem * CheckedUint32(vertCount); if (!checked_byteCount.isValid()) { ErrorInvalidValue("%s: Overflow in byteCount.", funcName); return false; } if (!mBoundVertexArray->mElementArrayBuffer) { ErrorInvalidOperation("%s: Must have element array buffer binding.", funcName); return false; } WebGLBuffer& elemArrayBuffer = *mBoundVertexArray->mElementArrayBuffer; if (!elemArrayBuffer.ByteLength()) { ErrorInvalidOperation("%s: Bound element array buffer doesn't have any data.", funcName); return false; } CheckedInt checked_neededByteCount = checked_byteCount.toChecked() + byteOffset; if (!checked_neededByteCount.isValid()) { ErrorInvalidOperation("%s: Overflow in byteOffset+byteCount.", funcName); return false; } if (uint32_t(checked_neededByteCount.value()) > elemArrayBuffer.ByteLength()) { ErrorInvalidOperation("%s: Bound element array buffer is too small for given" " count and offset.", funcName); return false; } if (!ValidateBufferFetching(funcName)) return false; if (!mMaxFetchedVertices || !elemArrayBuffer.ValidateIndexedFetch(type, mMaxFetchedVertices, first, vertCount)) { ErrorInvalidOperation("%s: bound vertex attribute buffers do not have sufficient " "size for given indices from the bound element array", funcName); return false; } 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::DrawElements(GLenum mode, GLsizei vertCount, GLenum type, WebGLintptr byteOffset, const char* funcName) { if (!funcName) { funcName = "drawElements"; } if (IsContextLost()) return; MakeContextCurrent(); bool error = false; ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error); if (error) return; const GLsizei instanceCount = 1; if (!DrawElements_check(funcName, mode, vertCount, type, byteOffset, instanceCount)) return; const ScopedDrawHelper scopedHelper(this, funcName, 0, mMaxFetchedVertices, instanceCount, &error); if (error) return; { ScopedDrawCallWrapper wrapper(*this); { UniquePtr errorScope; if (gl->IsANGLE()) { errorScope.reset(new gl::GLContext::LocalErrorScope(*gl)); } gl->fDrawElements(mode, vertCount, type, reinterpret_cast(byteOffset)); if (errorScope) { HandleDrawElementsErrors(this, funcName, *errorScope); } } } Draw_cleanup(funcName); } void WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei vertCount, GLenum type, WebGLintptr byteOffset, GLsizei instanceCount) { const char funcName[] = "drawElementsInstanced"; if (IsContextLost()) return; MakeContextCurrent(); bool error = false; ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error); if (error) return; if (!DrawElements_check(funcName, mode, vertCount, type, byteOffset, instanceCount)) return; if (!DrawInstanced_check(funcName)) return; const ScopedDrawHelper scopedHelper(this, funcName, 0, mMaxFetchedVertices, instanceCount, &error); if (error) return; { ScopedDrawCallWrapper wrapper(*this); { UniquePtr errorScope; if (gl->IsANGLE()) { errorScope.reset(new gl::GLContext::LocalErrorScope(*gl)); } gl->fDrawElementsInstanced(mode, vertCount, type, reinterpret_cast(byteOffset), instanceCount); 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 = mWidth; destHeight = mHeight; } 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; } } } /* * Verify that state is consistent for drawing, and compute max number of elements (maxAllowedCount) * that will be legal to be read from bound VBOs. */ bool WebGLContext::ValidateBufferFetching(const char* info) { MOZ_ASSERT(mCurrentProgram); // Note that mCurrentProgram->IsLinked() is NOT GUARANTEED. MOZ_ASSERT(mActiveProgramLinkInfo); #ifdef DEBUG GLint currentProgram = 0; MakeContextCurrent(); gl->fGetIntegerv(LOCAL_GL_CURRENT_PROGRAM, ¤tProgram); MOZ_ASSERT(GLuint(currentProgram) == mCurrentProgram->mGLName, "WebGL: current program doesn't agree with GL state"); #endif if (mBufferFetchingIsVerified) return true; bool hasPerVertex = false; uint32_t maxVertices = UINT32_MAX; uint32_t maxInstances = UINT32_MAX; const uint32_t attribCount = mBoundVertexArray->mAttribs.Length(); uint32_t i = 0; for (const auto& vd : mBoundVertexArray->mAttribs) { // If the attrib array isn't enabled, there's nothing to check; // it's a static value. if (!vd.mEnabled) continue; if (!vd.mBuf) { ErrorInvalidOperation("%s: no VBO bound to enabled vertex attrib index %du!", info, i); return false; } ++i; } mBufferFetch_IsAttrib0Active = false; for (const auto& attrib : mActiveProgramLinkInfo->attribs) { if (attrib.mLoc == -1) continue; const uint32_t attribLoc(attrib.mLoc); if (attribLoc >= attribCount) continue; if (attribLoc == 0) { mBufferFetch_IsAttrib0Active = true; } const auto& vd = mBoundVertexArray->mAttribs[attribLoc]; if (!vd.mEnabled) continue; const auto& bufByteLen = vd.mBuf->ByteLength(); if (vd.ByteOffset() > bufByteLen) { maxVertices = 0; maxInstances = 0; break; } size_t availBytes = bufByteLen - vd.ByteOffset(); if (vd.BytesPerVertex() > availBytes) { maxVertices = 0; maxInstances = 0; break; } availBytes -= vd.BytesPerVertex(); // Snip off the tail. const size_t vertCapacity = availBytes / vd.ExplicitStride() + 1; // Add +1 for the snipped tail. if (vd.mDivisor == 0) { if (vertCapacity < maxVertices) { maxVertices = vertCapacity; } hasPerVertex = true; } else { const auto curMaxInstances = CheckedInt(vertCapacity) * vd.mDivisor; // If this isn't valid, it's because we overflowed, which means we can support // *too much*. Don't update maxInstances in this case. if (curMaxInstances.isValid() && curMaxInstances.value() < maxInstances) { maxInstances = curMaxInstances.value(); } } } mBufferFetchingIsVerified = true; mBufferFetchingHasPerVertex = hasPerVertex; mMaxFetchedVertices = maxVertices; mMaxFetchedInstances = maxInstances; return true; } WebGLVertexAttrib0Status WebGLContext::WhatDoesVertexAttrib0Need() const { MOZ_ASSERT(mCurrentProgram); MOZ_ASSERT(mActiveProgramLinkInfo); const auto& isAttribArray0Enabled = mBoundVertexArray->mAttribs[0].mEnabled; 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 (isAttribArray0Enabled && mBufferFetch_IsAttrib0Active) return WebGLVertexAttrib0Status::Default; if (mBufferFetch_IsAttrib0Active) return WebGLVertexAttrib0Status::EmulatedInitializedArray; // Ensure that the legacy code has enough buffer. return WebGLVertexAttrib0Status::EmulatedUninitializedArray; } bool WebGLContext::DoFakeVertexAttrib0(const char* funcName, GLuint vertexCount) { if (!vertexCount) { vertexCount = 1; } 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(vertexCount) * bytesPerVert; if (!checked_dataSize.isValid()) { ErrorOutOfMemory("Integer overflow trying to construct a fake vertex attrib 0 array for a draw-operation " "with %d vertices. Try reducing the number of vertices.", vertexCount); 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::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 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); // We allocate our zeros on the heap, and we overallocate (16 bytes instead of 4) to // minimize the risk of running into a driver bug in texImage2D, as it is a bit // unusual maybe to create 1x1 textures, and the stack may not have the alignment that // TexImage2D expects. const webgl::DriverUnpackInfo dui = {texFormat, texFormat, LOCAL_GL_UNSIGNED_BYTE}; UniqueBuffer zeros = moz_xcalloc(1, 16); // 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->MakeCurrent(); mGL->fDeleteTextures(1, &mGLName); } } // namespace mozilla